From patchwork Tue Oct 1 12:14:58 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 1991448 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=E811d9cN; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=2620:52:3:1:0:246e:9693:128c; helo=server2.sourceware.org; envelope-from=libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [IPv6:2620:52:3:1:0:246e:9693:128c]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4XHxkC290Lz1xtY for ; Tue, 1 Oct 2024 22:16:19 +1000 (AEST) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 1F2B03842FEF for ; Tue, 1 Oct 2024 12:16:17 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by sourceware.org (Postfix) with ESMTP id 1360F3842FEF for ; Tue, 1 Oct 2024 12:15:05 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 1360F3842FEF Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 1360F3842FEF Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1727784909; cv=none; b=sR/U0dqo2DJ948KiodtSMTGVdFizJoX0g5O+jhbWWtaXUSbLcrIJTnht509AE7705agTNdsXcW/Z/IIdDAw/XvIGuKaZ0pcXc0ZaP1Rdn8c+UgNSMWxe90JC21HuczPi6Jxo3u5QsmmeG0MxcstcrtxNjRlu+NuPdkgHfNfBXgc= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1727784909; c=relaxed/simple; bh=fZBf1Z/DdcCTXSmADwKYsycEKQDnNxeye/iXqXG7zH8=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=rWAP5K4PHeq7fH9DFQS1ibD7+zQn4OoBXGwRS/vVyvzCVoUDodCdpfkOUQslvE2oyey9I2MQk+fFjenq4gpRa5mf7kmlaxuMnUvhIPwmrZ62Ju3V9ZKHQdJGyYtTVQJMwdSKwfEPwT9QOi+10e8K1rzuvHC9sTAJupxbX4YXb5s= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1727784904; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=SQaQCQie7AEJu7IB7m//X1lz211LAw6bsrBdyDA6OpA=; b=E811d9cNXNWKF9C72w46yb7wxd/3BMTFNuyeV3PJ/xKRS2jMYNF9tMKrK842MYylYeGics Rlm3rirG582b1NujCD0+H2EIe7jEBakVa6gV9vsY+aX1tgXmQ98ktmXN8VbaO/zwkqPUKD FKmiEkNno5C8V1J5wH9VoaP2uLOpSJg= Received: from mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-475-iAfmMXBTNMaUdCAY_iNr1Q-1; Tue, 01 Oct 2024 08:15:03 -0400 X-MC-Unique: iAfmMXBTNMaUdCAY_iNr1Q-1 Received: from mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (unknown [10.30.177.4]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-02.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id C084119626E2 for ; Tue, 1 Oct 2024 12:15:02 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.45.224.151]) by mx-prod-int-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id DF9F13003E4D for ; Tue, 1 Oct 2024 12:15:01 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH 3/4] time: Do not perform forced DST adjustment for DST-less zones In-Reply-To: Message-ID: <435ebea3d916f61759f8f3f3424365e2ccd5e810.1727784647.git.fweimer@redhat.com> References: X-From-Line: 435ebea3d916f61759f8f3f3424365e2ccd5e810 Mon Sep 17 00:00:00 2001 Date: Tue, 01 Oct 2024 14:14:58 +0200 User-Agent: Gnus/5.13 (Gnus v5.13) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.30.177.4 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~incoming=patchwork.ozlabs.org@sourceware.org For zones such as UTC, it does not really make sense to perform the forced adjustment. The timezone/testdata/IST file is the compiled Asia/Kolkata zone from tz 2024a. --- manual/time.texi | 41 ++++++++- time/Makefile | 1 + time/mktime.c | 8 +- time/tst-mktime-dst-adjust.c | 156 +++++++++++++++++++++++++++++++++++ timezone/testdata/IST | Bin 0 -> 285 bytes 5 files changed, 202 insertions(+), 4 deletions(-) create mode 100644 time/tst-mktime-dst-adjust.c create mode 100644 timezone/testdata/IST GIT binary patch literal 285 zcmWHE%1kq2zzf(I7#LU>7#M^a7#JAZ=kD2c>UNLD8P-CHGgFOLTq+To!N|nS#LUFN z5Of1%j<*{~wQmFi2LnTN1|yG;ZwQ00ZwP~Da0r98ftj%ZLkM9z*%=rZSQ!`?#Qy*P z|6gsFga(Lqe*W43M1$M_qCxHe(IB^g>8VC5Ks3}%EX*v-Fh>Pl06EIr4df`_2nHUo W(*!_H^T2Q(muGN@uBnx=0T%#v+DeN6 literal 0 HcmV?d00001 diff --git a/manual/time.texi b/manual/time.texi index 64aad8fdc5..c72febc7c6 100644 --- a/manual/time.texi +++ b/manual/time.texi @@ -1075,9 +1075,16 @@ This is a flag that indicates whether daylight saving time is (or was, or will be) in effect at the time described. The value is positive if daylight saving time is in effect, zero if it is not, and negative if the information is not available. -Although this flag is useful when passing a broken-down time to the -@code{mktime} function, for other uses this flag should be ignored and -the @code{tm_gmtoff} and @code{tm_zone} fields should be inspected instead. + +It is recommended to set this field to a negative value when calling +@code{mktime} unless the application has access to the correct value of +the daylight saving time status at the specified time. This instructs +@code{mktime} to use time zone data to determine whether or not to apply +daylight saving time. + +Reading the @code{tm_isdst} field after calling @code{localtime} +and similar functions is not recommended. Instead inspect the +the @code{tm_gmtoff} and @code{tm_zone} fields. @item long int tm_gmtoff This field describes the time zone that was used to compute this @@ -1292,6 +1299,33 @@ simple time representation. It also normalizes the contents of the broken-down time structure, and fills in some components based on the values of the others. +In general, it is recommended to set the @code{tm_isdst} member of +@var{brokentime} to a negative value prior to calling this function. +However, if @code{*@var{brokentime}} was produced by @code{localtime} or +similar functions, a correct value of @code{tm_isdst} allows to +disambiguate broken-down time representations during the overlapping +window after time jumped back during a daylight saving time transition. +This is a scenario where @code{tm_isdst} can be helpful, and the +adjustment described below does not produce unexpected @code{mktime} +results. + +If @code{tm_isdst} is negative, @code{mktime} uses time zone data to +determine whether daylight saving time is in effect at the requested +time. + +In the other case, @code{tm_isdst} is not negative: the value zero +indicates that no daylight saving time is in effect, and positive value +means it is in effect. If time zone data and the @code{tm_isdst} flag +disagree, @code{mktime} will forcefully adjust the returned value, using +an unspecified heuristic to obtain the daylight saving time offset. +Typically, this means that @code{mktime} adds one hour if +@code{tm_isdst} is zero, or subtracts one hour if @code{tm_isdst} is +positive. This forced adjustment only happens if time zone data +indicates that there is at least one daylight saving transition (at any +time). For zones such as @samp{UTC} which never use daylight saving +time, the adjustment does not happen even if @code{tm_isdst} is +positive. + The @code{mktime} function ignores the specified contents of the @code{tm_wday}, @code{tm_yday}, @code{tm_gmtoff}, and @code{tm_zone} members of the broken-down time @@ -1342,6 +1376,7 @@ available. @code{timelocal} is rather rare. @c tzfile_compute(!use_localtime) ok @code{timegm} is functionally identical to @code{mktime} except it +sets @code{@var{brokentime}->tm_isdst} to zero and always takes the input values to be UTC regardless of any local time zone setting. diff --git a/time/Makefile b/time/Makefile index d06797b06c..0cfc4c9d51 100644 --- a/time/Makefile +++ b/time/Makefile @@ -64,6 +64,7 @@ tests := \ tst-gmtime \ tst-itimer \ tst-mktime \ + tst-mktime-dst-adjust \ tst-mktime2 \ tst-mktime3 \ tst-mktime4 \ diff --git a/time/mktime.c b/time/mktime.c index 669bc46c34..ad371ba57f 100644 --- a/time/mktime.c +++ b/time/mktime.c @@ -433,7 +433,13 @@ __mktime_internal (struct tm *tp, bool local, mktime_offset_t *offset) /* We have a match. Check whether tm.tm_isdst has the requested value, if any. */ - if (isdst_differ (isdst, tm.tm_isdst)) + if (isdst_differ (isdst, tm.tm_isdst) +#if _LIBC + /* Only perform the adjustment if the time zone ever uses DST. + This skips the adjustment for UTC, for example. */ + && __daylight +#endif + ) { /* tm.tm_isdst has the wrong value. Look for a neighboring time with the right value, and use its UTC offset. diff --git a/time/tst-mktime-dst-adjust.c b/time/tst-mktime-dst-adjust.c new file mode 100644 index 0000000000..416ef2abcd --- /dev/null +++ b/time/tst-mktime-dst-adjust.c @@ -0,0 +1,156 @@ +/* Test mktime DST adjustment special cases. + Copyright (C) 2024 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#include +#include +#include + +static int +do_test (void) +{ + TEST_COMPARE (setenv ("TZ", "UTC", 1), 0); + + { + struct tm t = + { + .tm_year = 124, + .tm_mon = 9, + .tm_mday = 1, + .tm_hour = 9, + .tm_min = 20, + .tm_sec = 53, + .tm_isdst = 1, /* Not actually true. */ + }; + TEST_COMPARE (mktime (&t), 1727774453); + } + + /* IST used DST at one point, but no longer does. */ + { + char *path = realpath ("../timezone/testdata/IST", NULL); + TEST_VERIFY (path != NULL); + TEST_COMPARE (setenv ("TZ", path, 1), 0); + free (path); + } + + { + struct tm t = + { + .tm_year = 124, + .tm_mon = 9, + .tm_mday = 1, + .tm_hour = 9, + .tm_min = 20, + .tm_sec = 53, + .tm_isdst = 0, /* Correct value. */ + }; + TEST_COMPARE (mktime (&t), 1727774453 - (int) (5.5 * 3600)); + TEST_COMPARE (t.tm_gmtoff, (int) (5.5 * 3600)); + TEST_COMPARE (t.tm_isdst, 0); + } + + /* This value is incorrect, but the heuristic ignores historic + DST changes. */ + { + struct tm t = + { + .tm_year = 124, + .tm_mon = 9, + .tm_mday = 1, + .tm_hour = 9, + .tm_min = 20, + .tm_sec = 53, + .tm_isdst = 1, /* Incorrect value. */ + }; + TEST_COMPARE (mktime (&t), 1727774453 - (int) (5.5 * 3600)); + TEST_COMPARE (t.tm_gmtoff, (int) (5.5 * 3600)); + TEST_COMPARE (t.tm_isdst, 0); + } + + /* Test using correct DST. */ + { + struct tm t = + { + .tm_year = 42, + .tm_mon = 9, + .tm_mday = 1, + .tm_hour = 9, + .tm_min = 20, + .tm_sec = 53, + .tm_isdst = 1, /* Correct value, DST was in effect. */ + }; + TEST_COMPARE (mktime (&t), -860015347); + TEST_COMPARE (t.tm_gmtoff, (int) (6.5 * 3600)); + TEST_COMPARE (t.tm_isdst, 1); + } + + /* Mismatch: DST incorrectly claimed not in effect. */ + + { + struct tm t = + { + .tm_year = 42, + .tm_mon = 9, + .tm_mday = 1, + .tm_hour = 9, + .tm_min = 20, + .tm_sec = 53, + .tm_isdst = 0, /* Incorrect value. */ + }; + TEST_COMPARE (mktime (&t), -860015347 + 3600); /* One hour added. */ + TEST_COMPARE (t.tm_gmtoff, (int) (6.5 * 3600)); + TEST_COMPARE (t.tm_isdst, 1); + } + + /* Test using correct standard time. */ + { + struct tm t = + { + .tm_year = 42, + .tm_mon = 7, + .tm_mday = 1, + .tm_hour = 9, + .tm_min = 20, + .tm_sec = 53, + .tm_isdst = 0, /* Correct value, standard time in effect. */ + }; + TEST_COMPARE (mktime (&t), -865282147); + TEST_COMPARE (t.tm_gmtoff, (int) (5.5 * 3600)); + TEST_COMPARE (t.tm_isdst, 0); + } + + /* Test using standard time with mismatch. */ + { + struct tm t = + { + .tm_year = 42, + .tm_mon = 7, + .tm_mday = 1, + .tm_hour = 9, + .tm_min = 20, + .tm_sec = 53, + .tm_isdst = 1, /* Incorrect value. */ + }; + TEST_COMPARE (mktime (&t), -865282147 - 3600); /* One hour subtracted. */ + TEST_COMPARE (t.tm_gmtoff, (int) (5.5 * 3600)); + TEST_COMPARE (t.tm_isdst, 0); + } + + return 0; +} + +#include diff --git a/timezone/testdata/IST b/timezone/testdata/IST new file mode 100644 index 0000000000000000000000000000000000000000..0014046d29a38e9b8006f746fea794d7f71eb479