From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on inbox.vuxu.org X-Spam-Level: X-Spam-Status: No, score=-3.0 required=5.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, MAILING_LIST_MULTI,RCVD_IN_DNSWL_MED,RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL autolearn=ham autolearn_force=no version=3.4.4 Received: from second.openwall.net (second.openwall.net [193.110.157.125]) by inbox.vuxu.org (Postfix) with SMTP id 28B8221DB1 for ; Mon, 25 Mar 2024 13:21:07 +0100 (CET) Received: (qmail 18348 invoked by uid 550); 25 Mar 2024 12:16:22 -0000 Mailing-List: contact musl-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Reply-To: musl@lists.openwall.com Received: (qmail 18316 invoked from network); 25 Mar 2024 12:16:22 -0000 Date: Mon, 25 Mar 2024 08:21:13 -0400 From: Rich Felker To: Alexander Weps Cc: musl@lists.openwall.com Message-ID: <20240325122113.GB4163@brightrain.aerifal.cx> References: <20240324180200.GW4163@brightrain.aerifal.cx> <20240324182458.GX4163@brightrain.aerifal.cx> <9c2qfe36CoPBfKjzn1lDDZ_hfyNJCZW6-6ZTZlQgHAPr2djicIMMweEqUoQoQsDWsBt4AAZBL8vZlcsVCL950rYhcPpMDvhzDWean3oVHbs=@pm.me> <20240324192258.GY4163@brightrain.aerifal.cx> <-svm5EdX4OFN9hKzgS2FP6N1lgUGjT7edQONkAfCywgsRitwT6Vw22W3sUUGY_pnKGIXBKlujMZhPCDkJAMCYbBA5uF-IYgzhj8WB0wBE-A=@pm.me> <4YlR0YRqzZlDIOVv6SP8UDoop89n8u7BvQl_7eXNTvDZnogXMxG1z-TLGIBf-O4edUphddXGfADbk_d7Uzb37g5JoH7vOIvvNRMFDxPWZok=@pm.me> MIME-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline In-Reply-To: User-Agent: Mutt/1.5.21 (2010-09-15) Subject: Re: [musl] Broken mktime calculations when crossing DST boundary On Mon, Mar 25, 2024 at 11:52:00AM +0000, Alexander Weps wrote: > This is the simplest and most obvious example how broken the > calculation in musl is: > > void test10() > { > time_t t = 0; > struct tm tm = {0}; > char buf[64]; > > tm.tm_year = 2011 - 1900; > tm.tm_mon = 12 - 1; > tm.tm_mday = 29; > tm.tm_hour = 0; > tm.tm_min = 0; > tm.tm_sec = 0; > tm.tm_isdst = 0; > > strftime(buf, sizeof buf, "%F %T %Z", &tm); > printf("before: %s %ld %ld\n", buf, t, calc(&tm)); > > t = mktime(&tm); > > strftime(buf, sizeof buf, "%F %T %Z", &tm); > printf("after1: %s %ld %ld\n", buf, t, calc(&tm)); > > tm.tm_mday += 1; > t = mktime(&tm); > > strftime(buf, sizeof buf, "%F %T %Z", &tm); > printf("after2: %s %ld %ld\n", buf, t, calc(&tm)); > } > > TZ=Pacific/Apia > Year is greater than 1970. > > Input: > 2011-12-29 01:00:00 -10 > > Add a day: > tm.tm_mday += 1; > t = mktime(&tm); > > Output: > 2011-12-29 01:00:00 -10 > > Musl cannot reliably increment date by a day. Incrementing struct tm > representing 2011-12-29 01:00:00 -10 by one day leads to the same > date. > > Causing a program to loop or stack overflow. I thought you had found a real bug here, and spent some time working out the math by hand on paper because local time is so headbangingly awful and confusing. In the end, the conclusion I'm left with is that it's working just as expected. A "spring forward" like this is just like the start of DST, except that you can't disambiguate the does-not-exist time with an explicit tm_isdst. So all reasoning about what happens is equivalent to the much more familar case of start-of-DST with tm_isdst=-1. If you take your test program and switch it to initialize with tm_mday=31, then do -=1 instead of +=1, you'll find that it gives 2011-12-29 01:00:00 -10 as well, only now it seems like the correct, expected thing to happen. Any change to "fix" the case you're complaining about would *necessarily* break this case. You cannot iterate days by making relative changes to struct tm and calling mktime. This just does not work. You could instead iterate calendar day inputs yourself, throwing away duplicate outputs (resulting from nonexistent days like this one) but that would miss days that exist in duplicate on the calendar, where the change happens in the opposite direction. What's probably a better approach is iterating time_t values (or a struct tm in UTC, using timegm) then, for each day, converting to localtime and picking a "start of day" time in localtime. In any case, the core issue you're hitting here is that time zones are HARD to work with and that there is inherent complexity that libc cannot save you from. You only got lucky that what you were trying to do "worked" with glibc because you were iterating days forward; if you were doing reverse, it would break exactly the same way. Rich