/* Test that nstrftime works as required. Copyright (C) 2011-2023 Free Software Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* Written by Jim Meyering. */ #include #include "strftime.h" #include "intprops.h" #include #include #include #include #include #include #include "macros.h" #define STREQ(a, b) (strcmp (a, b) == 0) /* Support for settings like TZ='<+00>0' was added in IEEE Std 1003.1-2001. */ #define TZ_ANGLE_BRACKETS_SHOULD_WORK (200112 <= _POSIX_VERSION) struct posixtm_test { time_t in; int in_ns; char const *fmt; char const *exp; }; static struct posixtm_test const T[] = { { 1300000000, 0, "%F", "2011-03-13" }, { 0, 10, "%T.%N", "00:00:00.000000010" }, { 56, 123456789, "%T.%12N", "00:00:56.123456789000" }, { 0, 0, NULL, NULL } }; static int posixtm_test (void) { int fail = 0; unsigned int i; for (i = 0; T[i].fmt; i++) { char buf[1000]; time_t t = T[i].in; struct tm *tm = gmtime (&t); size_t n; ASSERT (tm); n = nstrftime (buf, sizeof buf, T[i].fmt, tm, 0, T[i].in_ns); if (n == 0) { fail = 1; printf ("nstrftime failed with format %s\n", T[i].fmt); } if (! STREQ (buf, T[i].exp)) { fail = 1; printf ("%s: result mismatch: got %s, expected %s\n", T[i].fmt, buf, T[i].exp); } } return fail; } struct tzalloc_test { timezone_t tz; char const *setting; }; static struct tzalloc_test TZ[] = { #define Pacific 0 { 0, "PST8PDT,M3.2.0,M11.1.0" }, #define Arizona 1 { 0, "MST7" }, #define UTC 2 { 0, 0 }, #define CentEur 3 { 0, "CET-1CEST,M3.5.0,M10.5.0/3" }, #define Japan 4 { 0, "JST-9" }, #define NZ 5 { 0, "NZST-12NZDT,M9.5.0,M4.1.0/3" }, #define Unknown 6 { 0, "<-00>0" }, { 0 } }; struct localtime_rz_test { /* Input parameters. */ struct tzalloc_test *tza; time_t t; /* Expected result. */ char const *exp; /* Determines if an incorrectly unset tm_isdst results in failure or just a warning. */ int ahistorical; }; static struct localtime_rz_test LT[] = { { TZ+Pacific, 0, "1969-12-31 16:00:00 -0800 (PST)", 0 }, { TZ+Arizona, 0, "1969-12-31 17:00:00 -0700 (MST)", 0 }, { TZ+UTC , 0, "1970-01-01 00:00:00 +0000 (UTC)", 0 }, { TZ+CentEur, 0, "1970-01-01 01:00:00 +0100 (CET)", 0 }, { TZ+Japan , 0, "1970-01-01 09:00:00 +0900 (JST)", 0 }, { TZ+NZ , 0, "1970-01-01 13:00:00 +1300 (NZDT)", 1 }, { TZ+Pacific, 500000001, "1985-11-04 16:53:21 -0800 (PST)", 0 }, { TZ+Arizona, 500000001, "1985-11-04 17:53:21 -0700 (MST)", 0 }, { TZ+UTC , 500000001, "1985-11-05 00:53:21 +0000 (UTC)", 0 }, { TZ+CentEur, 500000001, "1985-11-05 01:53:21 +0100 (CET)", 1 }, { TZ+Japan , 500000001, "1985-11-05 09:53:21 +0900 (JST)", 0 }, { TZ+NZ , 500000001, "1985-11-05 13:53:21 +1300 (NZDT)", 0 }, { TZ+Pacific, 1000000002, "2001-09-08 18:46:42 -0700 (PDT)", 0 }, { TZ+Arizona, 1000000002, "2001-09-08 18:46:42 -0700 (MST)", 0 }, { TZ+UTC , 1000000002, "2001-09-09 01:46:42 +0000 (UTC)", 0 }, { TZ+CentEur, 1000000002, "2001-09-09 03:46:42 +0200 (CEST)", 0 }, { TZ+Japan , 1000000002, "2001-09-09 10:46:42 +0900 (JST)", 0 }, { TZ+NZ , 1000000002, "2001-09-09 13:46:42 +1200 (NZST)", 0 }, #if TZ_ANGLE_BRACKETS_SHOULD_WORK { TZ+Unknown, 0, "1970-01-01 00:00:00 -0000 (-00)", 0 }, { TZ+Unknown, 500000001, "1985-11-05 00:53:21 -0000 (-00)", 0 }, { TZ+Unknown, 1000000002, "2001-09-09 01:46:42 -0000 (-00)", 0 }, #endif { 0 } }; static int tzalloc_test (void) { int fail = 0; int i; for (i = 0; LT[i].tza; i++) { struct tzalloc_test *tza = LT[i].tza; long lt = LT[i].t; timezone_t tz = tza->tz; char const *setting; static char const format[] = "%Y-%m-%d %H:%M:%S %z (%Z)"; char buf[1000]; struct tm tm; size_t n; if (!tz && tza->setting) { tz = tzalloc (tza->setting); if (!tz) { fail = 1; printf ("%s: tzalloc: %s\n", TZ[i].setting, strerror (errno)); continue; } tza->tz = tz; } setting = tza->setting ? tza->setting : "UTC0"; if (!localtime_rz (tz, <[i].t, &tm)) { fail = 1; printf ("%s: %ld: localtime_rz: %s\n", setting, lt, strerror (errno)); continue; } n = nstrftime (buf, sizeof buf, format, &tm, tz, 0); if (n == 0) { fail = 1; printf ("%s: %ld: nstrftime failed\n", setting, lt); continue; } if (! (STREQ (buf, LT[i].exp) || (!tz && n == strlen (LT[i].exp) && memcmp (buf, LT[i].exp, n - sizeof "(GMT)" + 1) == 0 && STREQ (buf + n - sizeof "(GMT)" + 1, "(GMT)")))) { /* Don't fail for unhandled dst in ahistorical entries, as gnulib doesn't currently fix that issue, seen on Darwin 14. */ if (!LT[i].ahistorical || tm.tm_isdst) fail = 1; printf ("%s: expected \"%s\", got \"%s\"\n", setting, LT[i].exp, buf); } } return fail; } static int quarter_test (void) { int result = 0; size_t mon; /* Check %q. */ for (mon = 1; mon <= 12; mon++) { char out[2]; char exp[2] = {0,}; struct tm qtm = { .tm_mon = mon - 1 }; char fmt[3] = {'%','q','\0'}; size_t r = nstrftime (out, sizeof (out), fmt, &qtm, 0, 0); if (r == 0) { puts ("nstrftime(\"%q\") failed"); result = 1; break; } exp[0] = mon < 4 ? '1' : mon < 7 ? '2' : mon < 10 ? '3' : '4'; if (strcmp (out, exp) != 0) { printf ("nstrftime %%q: expected \"%s\", got \"%s\"\n", exp, out); result = 1; break; } } return result; } static int errno_test (void) { int fail = 0; struct tm tm = { .tm_year = 2020 - 1900, .tm_mday = 1 }; char buf[INT_BUFSIZE_BOUND (time_t)]; size_t n; int bigyear = LLONG_MAX - 1900 < INT_MAX ? LLONG_MAX - 1900 : INT_MAX; errno = 0; n = nstrftime (buf, 0, "%m", &tm, 0, 0); if (! (n == 0 && errno == ERANGE)) { fail = 1; printf ("nstrftime failed to set errno = ERANGE\n"); } errno = 0; n = nstrftime (buf, sizeof buf, "", &tm, 0, 0); if (! (n == 0 && errno == 0)) { fail = 1; printf ("nstrftime failed to leave errno alone\n"); } tm.tm_year = bigyear; errno = 0; n = nstrftime (buf, sizeof buf, "%s", &tm, 0, 0); if (n == 0) { if (errno != EOVERFLOW) { fail = 1; printf ("nstrftime failed to set errno = EOVERFLOW\n"); } if (mktime_z (0, &tm) != (time_t) -1) { fail = 1; printf ("nstrftime %%s failed but mktime_z worked for tm_year=%d\n", bigyear); } } else { long long int text_seconds = atoll (buf); if (text_seconds <= (LLONG_MAX - 1 < TYPE_MAXIMUM (time_t) ? LLONG_MAX - 1 : TYPE_MAXIMUM (time_t))) { time_t bigtime = text_seconds; struct tm *tmp = gmtime (&bigtime); if (!tmp) { fail = 1; printf ("gmtime failed on nstrftime result\n"); } else { char buf1[sizeof buf]; size_t n1 = nstrftime (buf1, sizeof buf1, "%s", tmp, 0, 0); buf1[n1] = '\0'; if (! STREQ (buf, buf1)) { fail = 1; printf ("nstrftime %%s first returned '%s', then '%s'\n", buf, buf1); } } } } return fail; } int main (void) { int fail = 0; fail |= posixtm_test (); fail |= tzalloc_test (); fail |= quarter_test (); fail |= errno_test (); return fail; } /* Local Variables: indent-tabs-mode: nil End: */