Index: head/contrib/netbsd-tests/lib/libc/time/t_strptime.c =================================================================== --- head/contrib/netbsd-tests/lib/libc/time/t_strptime.c (revision 340105) +++ head/contrib/netbsd-tests/lib/libc/time/t_strptime.c (revision 340106) @@ -1,513 +1,524 @@ /* $NetBSD: t_strptime.c,v 1.12 2015/10/31 02:25:11 christos Exp $ */ /*- * Copyright (c) 1998, 2008 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by David Laight. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include __COPYRIGHT("@(#) Copyright (c) 2008\ The NetBSD Foundation, inc. All rights reserved."); __RCSID("$NetBSD: t_strptime.c,v 1.12 2015/10/31 02:25:11 christos Exp $"); #include #include #include #include static void h_pass(const char *buf, const char *fmt, int len, int tm_sec, int tm_min, int tm_hour, int tm_mday, int tm_mon, int tm_year, int tm_wday, int tm_yday) { struct tm tm = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, NULL }; const char *ret, *exp; exp = buf + len; ret = strptime(buf, fmt, &tm); #ifdef __FreeBSD__ ATF_CHECK_MSG(ret == exp, "strptime(\"%s\", \"%s\", tm): incorrect return code: " "expected: %p, got: %p", buf, fmt, exp, ret); #define H_REQUIRE_FIELD(field) \ ATF_CHECK_MSG(tm.field == field, \ "strptime(\"%s\", \"%s\", tm): incorrect %s: " \ "expected: %d, but got: %d", buf, fmt, \ ___STRING(field), field, tm.field) #else ATF_REQUIRE_MSG(ret == exp, "strptime(\"%s\", \"%s\", tm): incorrect return code: " "expected: %p, got: %p", buf, fmt, exp, ret); #define H_REQUIRE_FIELD(field) \ ATF_REQUIRE_MSG(tm.field == field, \ "strptime(\"%s\", \"%s\", tm): incorrect %s: " \ "expected: %d, but got: %d", buf, fmt, \ ___STRING(field), field, tm.field) #endif H_REQUIRE_FIELD(tm_sec); H_REQUIRE_FIELD(tm_min); H_REQUIRE_FIELD(tm_hour); H_REQUIRE_FIELD(tm_mday); H_REQUIRE_FIELD(tm_mon); H_REQUIRE_FIELD(tm_year); H_REQUIRE_FIELD(tm_wday); H_REQUIRE_FIELD(tm_yday); #undef H_REQUIRE_FIELD } static void h_fail(const char *buf, const char *fmt) { struct tm tm = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, NULL }; #ifdef __FreeBSD__ ATF_CHECK_MSG(strptime(buf, fmt, &tm) == NULL, "strptime(\"%s\", " "\"%s\", &tm) should fail, but it didn't", buf, fmt); #else ATF_REQUIRE_MSG(strptime(buf, fmt, &tm) == NULL, "strptime(\"%s\", " "\"%s\", &tm) should fail, but it didn't", buf, fmt); #endif } static struct { const char *name; long offs; } zt[] = { #ifndef __FreeBSD__ { "Z", 0 }, { "UT", 0 }, { "UTC", 0 }, { "GMT", 0 }, { "EST", -18000 }, { "EDT", -14400 }, { "CST", -21600 }, { "CDT", -18000 }, { "MST", -25200 }, { "MDT", -21600 }, { "PST", -28800 }, { "PDT", -25200 }, { "VST", -1 }, { "VDT", -1 }, { "+03", 10800 }, { "-03", -10800 }, { "+0403", 14580 }, { "-0403", -14580 }, { "+04:03", 14580 }, { "-04:03", -14580 }, { "+14:00", 50400 }, { "-14:00", -50400 }, { "+23:59", 86340 }, { "-23:59", -86340 }, { "1", -1 }, { "03", -1 }, { "0304", -1 }, { "+1", -1 }, { "-203", -1 }, { "+12345", -1 }, { "+12:345", -1 }, { "+123:45", -1 }, { "+2400", -1 }, { "-2400", -1 }, { "+1060", -1 }, { "-1060", -1 }, { "A", -3600 }, { "B", -7200 }, { "C", -10800 }, { "D", -14400 }, { "E", -18000 }, { "F", -21600 }, { "G", -25200 }, { "H", -28800 }, { "I", -32400 }, { "L", -39600 }, { "M", -43200 }, { "N", 3600 }, { "O", 7200 }, { "P", 10800 }, { "Q", 14400 }, { "R", 18000 }, { "T", 25200 }, { "U", 28800 }, { "V", 32400 }, { "W", 36000 }, { "X", 39600 }, { "Y", 43200 }, { "J", -2 }, { "America/Los_Angeles", -28800 }, { "America/New_York", -18000 }, { "EST4EDT", -14400 }, { "Bogus", -1 }, #endif }; static void ztest1(const char *name, const char *fmt, long value) { struct tm tm; char *rv; memset(&tm, 0, sizeof(tm)); if ((rv = strptime(name, fmt, &tm)) == NULL) tm.tm_gmtoff = -1; else if (rv == name && fmt[1] == 'Z') value = 0; switch (value) { #ifndef __FreeBSD__ case -2: value = -timezone; break; #endif case -1: if (fmt[1] == 'Z') value = 0; break; default: break; } ATF_REQUIRE_MSG(tm.tm_gmtoff == value, "strptime(\"%s\", \"%s\", &tm): " "expected: tm.tm_gmtoff=%ld, got: tm.tm_gmtoff=%ld", name, fmt, value, tm.tm_gmtoff); printf("%s %s %ld\n", name, fmt, tm.tm_gmtoff); } static void ztest(const char *fmt) { setenv("TZ", "US/Eastern", 1); #ifndef __FreeBSD__ ztest1("GMT", fmt, 0); ztest1("UTC", fmt, 0); ztest1("US/Eastern", fmt, -18000); #endif for (size_t i = 0; i < __arraycount(zt); i++) ztest1(zt[i].name, fmt, zt[i].offs); } ATF_TC(common); ATF_TC_HEAD(common, tc) { atf_tc_set_md_var(tc, "descr", "Checks strptime(3): various checks"); } ATF_TC_BODY(common, tc) { h_pass("Tue Jan 20 23:27:46 1998", "%a %b %d %T %Y", 24, 46, 27, 23, 20, 0, 98, 2, 19); h_pass("Tue Jan 20 23:27:46 1998", "%a %b %d %H:%M:%S %Y", 24, 46, 27, 23, 20, 0, 98, 2, 19); h_pass("Tue Jan 20 23:27:46 1998", "%c", 24, 46, 27, 23, 20, 0, 98, 2, 19); h_pass("Fri Mar 4 20:05:34 2005", "%a %b %e %H:%M:%S %Y", 24, 34, 5, 20, 4, 2, 105, 5, 62); h_pass("5\t3 4 8pm:05:34 2005", "%w%n%m%t%d%n%k%p:%M:%S %Y", 21, 34, 5, 20, 4, 2, 105, 5, 62); h_pass("Fri Mar 4 20:05:34 2005", "%c", 24, 34, 5, 20, 4, 2, 105, 5, 62); } ATF_TC(day); ATF_TC_HEAD(day, tc) { atf_tc_set_md_var(tc, "descr", "Checks strptime(3) day name conversions [aA]"); } ATF_TC_BODY(day, tc) { h_pass("Sun", "%a", 3, -1, -1, -1, -1, -1, -1, 0, -1); h_pass("Sunday", "%a", 6, -1, -1, -1, -1, -1, -1, 0, -1); h_pass("Mon", "%a", 3, -1, -1, -1, -1, -1, -1, 1, -1); h_pass("Monday", "%a", 6, -1, -1, -1, -1, -1, -1, 1, -1); h_pass("Tue", "%a", 3, -1, -1, -1, -1, -1, -1, 2, -1); h_pass("Tuesday", "%a", 7, -1, -1, -1, -1, -1, -1, 2, -1); h_pass("Wed", "%a", 3, -1, -1, -1, -1, -1, -1, 3, -1); h_pass("Wednesday", "%a", 9, -1, -1, -1, -1, -1, -1, 3, -1); h_pass("Thu", "%a", 3, -1, -1, -1, -1, -1, -1, 4, -1); h_pass("Thursday", "%a", 8, -1, -1, -1, -1, -1, -1, 4, -1); h_pass("Fri", "%a", 3, -1, -1, -1, -1, -1, -1, 5, -1); h_pass("Friday", "%a", 6, -1, -1, -1, -1, -1, -1, 5, -1); h_pass("Sat", "%a", 3, -1, -1, -1, -1, -1, -1, 6, -1); h_pass("Saturday", "%a", 8, -1, -1, -1, -1, -1, -1, 6, -1); h_pass("Saturn", "%a", 3, -1, -1, -1, -1, -1, -1, 6, -1); h_fail("Moon", "%a"); h_pass("Sun", "%A", 3, -1, -1, -1, -1, -1, -1, 0, -1); h_pass("Sunday", "%A", 6, -1, -1, -1, -1, -1, -1, 0, -1); h_pass("Mon", "%A", 3, -1, -1, -1, -1, -1, -1, 1, -1); h_pass("Monday", "%A", 6, -1, -1, -1, -1, -1, -1, 1, -1); h_pass("Tue", "%A", 3, -1, -1, -1, -1, -1, -1, 2, -1); h_pass("Tuesday", "%A", 7, -1, -1, -1, -1, -1, -1, 2, -1); h_pass("Wed", "%A", 3, -1, -1, -1, -1, -1, -1, 3, -1); h_pass("Wednesday", "%A", 9, -1, -1, -1, -1, -1, -1, 3, -1); h_pass("Thu", "%A", 3, -1, -1, -1, -1, -1, -1, 4, -1); h_pass("Thursday", "%A", 8, -1, -1, -1, -1, -1, -1, 4, -1); h_pass("Fri", "%A", 3, -1, -1, -1, -1, -1, -1, 5, -1); h_pass("Friday", "%A", 6, -1, -1, -1, -1, -1, -1, 5, -1); h_pass("Sat", "%A", 3, -1, -1, -1, -1, -1, -1, 6, -1); h_pass("Saturday", "%A", 8, -1, -1, -1, -1, -1, -1, 6, -1); h_pass("Saturn", "%A", 3, -1, -1, -1, -1, -1, -1, 6, -1); h_fail("Moon", "%A"); h_pass("mon", "%a", 3, -1, -1, -1, -1, -1, -1, 1, -1); h_pass("tueSDay", "%A", 7, -1, -1, -1, -1, -1, -1, 2, -1); h_pass("sunday", "%A", 6, -1, -1, -1, -1, -1, -1, 0, -1); #ifdef __NetBSD__ h_fail("sunday", "%EA"); #else h_pass("Sunday", "%EA", 6, -1, -1, -1, -1, -1, -1, 0, -1); #endif h_pass("SaturDay", "%A", 8, -1, -1, -1, -1, -1, -1, 6, -1); #ifdef __NetBSD__ h_fail("SaturDay", "%OA"); #else h_pass("SaturDay", "%OA", 8, -1, -1, -1, -1, -1, -1, 6, -1); #endif #ifdef __FreeBSD__ h_fail("00", "%d"); #endif } ATF_TC(hour); ATF_TC_HEAD(hour, tc) { atf_tc_set_md_var(tc, "descr", #ifdef __FreeBSD__ "Checks strptime(3) hour conversions [HIkl]"); #else "Checks strptime(3) hour conversions [IH]"); #endif } ATF_TC_BODY(hour, tc) { h_fail("00", "%I"); h_fail("13", "%I"); + #ifdef __FreeBSD__ - h_fail("00", "%l"); + h_pass("0", "%k", 1, -1, -1, 0, -1, -1, -1, -1, -1); + h_pass("04", "%k", 2, -1, -1, 4, -1, -1, -1, -1, -1); + h_pass(" 8", "%k", 2, -1, -1, 8, -1, -1, -1, -1, -1); + h_pass("23", "%k", 2, -1, -1, 23, -1, -1, -1, -1, -1); + h_fail("24", "%k"); + + h_fail("0", "%l"); + h_pass("1", "%l", 1, -1, -1, 1, -1, -1, -1, -1, -1); + h_pass("05", "%l", 2, -1, -1, 5, -1, -1, -1, -1, -1); + h_pass(" 9", "%l", 2, -1, -1, 9, -1, -1, -1, -1, -1); + h_pass("12", "%l", 2, -1, -1, 12, -1, -1, -1, -1, -1); h_fail("13", "%l"); #endif h_pass("00", "%H", 2, -1, -1, 0, -1, -1, -1, -1, -1); h_pass("12", "%H", 2, -1, -1, 12, -1, -1, -1, -1, -1); h_pass("23", "%H", 2, -1, -1, 23, -1, -1, -1, -1, -1); h_fail("24", "%H"); } ATF_TC(month); ATF_TC_HEAD(month, tc) { atf_tc_set_md_var(tc, "descr", "Checks strptime(3) month name conversions [bB]"); } ATF_TC_BODY(month, tc) { h_pass("Jan", "%b", 3, -1, -1, -1, -1, 0, -1, -1, -1); h_pass("January", "%b", 7, -1, -1, -1, -1, 0, -1, -1, -1); h_pass("Feb", "%b", 3, -1, -1, -1, -1, 1, -1, -1, -1); h_pass("February", "%b", 8, -1, -1, -1, -1, 1, -1, -1, -1); h_pass("Mar", "%b", 3, -1, -1, -1, -1, 2, -1, -1, -1); h_pass("March", "%b", 5, -1, -1, -1, -1, 2, -1, -1, -1); h_pass("Apr", "%b", 3, -1, -1, -1, -1, 3, -1, -1, -1); h_pass("April", "%b", 5, -1, -1, -1, -1, 3, -1, -1, -1); h_pass("May", "%b", 3, -1, -1, -1, -1, 4, -1, -1, -1); h_pass("Jun", "%b", 3, -1, -1, -1, -1, 5, -1, -1, -1); h_pass("June", "%b", 4, -1, -1, -1, -1, 5, -1, -1, -1); h_pass("Jul", "%b", 3, -1, -1, -1, -1, 6, -1, -1, -1); h_pass("July", "%b", 4, -1, -1, -1, -1, 6, -1, -1, -1); h_pass("Aug", "%b", 3, -1, -1, -1, -1, 7, -1, -1, -1); h_pass("August", "%b", 6, -1, -1, -1, -1, 7, -1, -1, -1); h_pass("Sep", "%b", 3, -1, -1, -1, -1, 8, -1, -1, -1); h_pass("September", "%b", 9, -1, -1, -1, -1, 8, -1, -1, -1); h_pass("Oct", "%b", 3, -1, -1, -1, -1, 9, -1, -1, -1); h_pass("October", "%b", 7, -1, -1, -1, -1, 9, -1, -1, -1); h_pass("Nov", "%b", 3, -1, -1, -1, -1, 10, -1, -1, -1); h_pass("November", "%b", 8, -1, -1, -1, -1, 10, -1, -1, -1); h_pass("Dec", "%b", 3, -1, -1, -1, -1, 11, -1, -1, -1); h_pass("December", "%b", 8, -1, -1, -1, -1, 11, -1, -1, -1); h_pass("Mayor", "%b", 3, -1, -1, -1, -1, 4, -1, -1, -1); h_pass("Mars", "%b", 3, -1, -1, -1, -1, 2, -1, -1, -1); h_fail("Rover", "%b"); h_pass("Jan", "%B", 3, -1, -1, -1, -1, 0, -1, -1, -1); h_pass("January", "%B", 7, -1, -1, -1, -1, 0, -1, -1, -1); h_pass("Feb", "%B", 3, -1, -1, -1, -1, 1, -1, -1, -1); h_pass("February", "%B", 8, -1, -1, -1, -1, 1, -1, -1, -1); h_pass("Mar", "%B", 3, -1, -1, -1, -1, 2, -1, -1, -1); h_pass("March", "%B", 5, -1, -1, -1, -1, 2, -1, -1, -1); h_pass("Apr", "%B", 3, -1, -1, -1, -1, 3, -1, -1, -1); h_pass("April", "%B", 5, -1, -1, -1, -1, 3, -1, -1, -1); h_pass("May", "%B", 3, -1, -1, -1, -1, 4, -1, -1, -1); h_pass("Jun", "%B", 3, -1, -1, -1, -1, 5, -1, -1, -1); h_pass("June", "%B", 4, -1, -1, -1, -1, 5, -1, -1, -1); h_pass("Jul", "%B", 3, -1, -1, -1, -1, 6, -1, -1, -1); h_pass("July", "%B", 4, -1, -1, -1, -1, 6, -1, -1, -1); h_pass("Aug", "%B", 3, -1, -1, -1, -1, 7, -1, -1, -1); h_pass("August", "%B", 6, -1, -1, -1, -1, 7, -1, -1, -1); h_pass("Sep", "%B", 3, -1, -1, -1, -1, 8, -1, -1, -1); h_pass("September", "%B", 9, -1, -1, -1, -1, 8, -1, -1, -1); h_pass("Oct", "%B", 3, -1, -1, -1, -1, 9, -1, -1, -1); h_pass("October", "%B", 7, -1, -1, -1, -1, 9, -1, -1, -1); h_pass("Nov", "%B", 3, -1, -1, -1, -1, 10, -1, -1, -1); h_pass("November", "%B", 8, -1, -1, -1, -1, 10, -1, -1, -1); h_pass("Dec", "%B", 3, -1, -1, -1, -1, 11, -1, -1, -1); h_pass("December", "%B", 8, -1, -1, -1, -1, 11, -1, -1, -1); h_pass("Mayor", "%B", 3, -1, -1, -1, -1, 4, -1, -1, -1); h_pass("Mars", "%B", 3, -1, -1, -1, -1, 2, -1, -1, -1); h_fail("Rover", "%B"); h_pass("september", "%b", 9, -1, -1, -1, -1, 8, -1, -1, -1); h_pass("septembe", "%B", 3, -1, -1, -1, -1, 8, -1, -1, -1); } ATF_TC(seconds); ATF_TC_HEAD(seconds, tc) { atf_tc_set_md_var(tc, "descr", "Checks strptime(3) seconds conversions [S]"); } ATF_TC_BODY(seconds, tc) { h_pass("0", "%S", 1, 0, -1, -1, -1, -1, -1, -1, -1); h_pass("59", "%S", 2, 59, -1, -1, -1, -1, -1, -1, -1); h_pass("60", "%S", 2, 60, -1, -1, -1, -1, -1, -1, -1); #ifdef __FreeBSD__ /* * (Much) older versions of the standard (up to the Issue 6) allowed for * [0;61] range in %S conversion for double-leap seconds, and it's * apparently what NetBSD and glibc are expecting, however current * version defines allowed values to be [0;60], and that is what our * strptime() implementation expects. */ h_fail("61", "%S"); #else h_pass("61", "%S", 2, 61, -1, -1, -1, -1, -1, -1, -1); #endif h_fail("62", "%S"); } ATF_TC(year); ATF_TC_HEAD(year, tc) { atf_tc_set_md_var(tc, "descr", "Checks strptime(3) century/year conversions [CyY]"); } ATF_TC_BODY(year, tc) { h_pass("x20y", "x%Cy", 4, -1, -1, -1, -1, -1, 100, -1, -1); h_pass("x84y", "x%yy", 4, -1, -1, -1, -1, -1, 84, -1, -1); h_pass("x2084y", "x%C%yy", 6, -1, -1, -1, -1, -1, 184, -1, -1); h_pass("x8420y", "x%y%Cy", 6, -1, -1, -1, -1, -1, 184, -1, -1); h_pass("%20845", "%%%C%y5", 6, -1, -1, -1, -1, -1, 184, -1, -1); #ifndef __FreeBSD__ h_fail("%", "%E%"); #endif h_pass("1980", "%Y", 4, -1, -1, -1, -1, -1, 80, -1, -1); h_pass("1980", "%EY", 4, -1, -1, -1, -1, -1, 80, -1, -1); } ATF_TC(zone); ATF_TC_HEAD(zone, tc) { atf_tc_set_md_var(tc, "descr", "Checks strptime(3) timezone conversion [z]"); } ATF_TC_BODY(zone, tc) { ztest("%z"); } ATF_TC(Zone); ATF_TC_HEAD(Zone, tc) { atf_tc_set_md_var(tc, "descr", "Checks strptime(3) timezone conversion [Z]"); } ATF_TC_BODY(Zone, tc) { ztest("%Z"); } ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, common); ATF_TP_ADD_TC(tp, day); ATF_TP_ADD_TC(tp, hour); ATF_TP_ADD_TC(tp, month); ATF_TP_ADD_TC(tp, seconds); ATF_TP_ADD_TC(tp, year); ATF_TP_ADD_TC(tp, zone); ATF_TP_ADD_TC(tp, Zone); return atf_no_error(); } Index: head/lib/libc/stdtime/strptime.c =================================================================== --- head/lib/libc/stdtime/strptime.c (revision 340105) +++ head/lib/libc/stdtime/strptime.c (revision 340106) @@ -1,717 +1,724 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2014 Gary Mills * Copyright 2011, Nexenta Systems, Inc. All rights reserved. * Copyright (c) 1994 Powerdog Industries. All rights reserved. * * Copyright (c) 2011 The FreeBSD Foundation * All rights reserved. * Portions of this software were developed by David Chisnall * under sponsorship from the FreeBSD Foundation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY POWERDOG INDUSTRIES ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE POWERDOG INDUSTRIES BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * The views and conclusions contained in the software and documentation * are those of the authors and should not be interpreted as representing * official policies, either expressed or implied, of Powerdog Industries. */ #include #ifndef lint #ifndef NOID static char copyright[] __unused = "@(#) Copyright (c) 1994 Powerdog Industries. All rights reserved."; static char sccsid[] __unused = "@(#)strptime.c 0.1 (Powerdog) 94/03/27"; #endif /* !defined NOID */ #endif /* not lint */ __FBSDID("$FreeBSD$"); #include "namespace.h" #include #include #include #include #include #include #include "un-namespace.h" #include "libc_private.h" #include "timelocal.h" #include "tzfile.h" static char * _strptime(const char *, const char *, struct tm *, int *, locale_t); #define asizeof(a) (sizeof(a) / sizeof((a)[0])) #define FLAG_NONE (1 << 0) #define FLAG_YEAR (1 << 1) #define FLAG_MONTH (1 << 2) #define FLAG_YDAY (1 << 3) #define FLAG_MDAY (1 << 4) #define FLAG_WDAY (1 << 5) /* * Calculate the week day of the first day of a year. Valid for * the Gregorian calendar, which began Sept 14, 1752 in the UK * and its colonies. Ref: * http://en.wikipedia.org/wiki/Determination_of_the_day_of_the_week */ static int first_wday_of(int year) { return (((2 * (3 - (year / 100) % 4)) + (year % 100) + ((year % 100) / 4) + (isleap(year) ? 6 : 0) + 1) % 7); } static char * _strptime(const char *buf, const char *fmt, struct tm *tm, int *GMTp, locale_t locale) { char c; const char *ptr; int day_offset = -1, wday_offset; int week_offset; int i, len; int flags; int Ealternative, Oalternative; int century, year; const struct lc_time_T *tptr = __get_current_time_locale(locale); static int start_of_month[2][13] = { {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365}, {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366} }; flags = FLAG_NONE; century = -1; year = -1; ptr = fmt; while (*ptr != 0) { c = *ptr++; if (c != '%') { if (isspace_l((unsigned char)c, locale)) while (*buf != 0 && isspace_l((unsigned char)*buf, locale)) buf++; else if (c != *buf++) return (NULL); continue; } Ealternative = 0; Oalternative = 0; label: c = *ptr++; switch (c) { case '%': if (*buf++ != '%') return (NULL); break; case '+': buf = _strptime(buf, tptr->date_fmt, tm, GMTp, locale); if (buf == NULL) return (NULL); flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; break; case 'C': if (!isdigit_l((unsigned char)*buf, locale)) return (NULL); /* XXX This will break for 3-digit centuries. */ len = 2; for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, locale); buf++) { i *= 10; i += *buf - '0'; len--; } century = i; flags |= FLAG_YEAR; break; case 'c': buf = _strptime(buf, tptr->c_fmt, tm, GMTp, locale); if (buf == NULL) return (NULL); flags |= FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; break; case 'D': buf = _strptime(buf, "%m/%d/%y", tm, GMTp, locale); if (buf == NULL) return (NULL); flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; break; case 'E': if (Ealternative || Oalternative) break; Ealternative++; goto label; case 'O': if (Ealternative || Oalternative) break; Oalternative++; goto label; case 'F': buf = _strptime(buf, "%Y-%m-%d", tm, GMTp, locale); if (buf == NULL) return (NULL); flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; break; case 'R': buf = _strptime(buf, "%H:%M", tm, GMTp, locale); if (buf == NULL) return (NULL); break; case 'r': buf = _strptime(buf, tptr->ampm_fmt, tm, GMTp, locale); if (buf == NULL) return (NULL); break; case 'T': buf = _strptime(buf, "%H:%M:%S", tm, GMTp, locale); if (buf == NULL) return (NULL); break; case 'X': buf = _strptime(buf, tptr->X_fmt, tm, GMTp, locale); if (buf == NULL) return (NULL); break; case 'x': buf = _strptime(buf, tptr->x_fmt, tm, GMTp, locale); if (buf == NULL) return (NULL); flags |= FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; break; case 'j': if (!isdigit_l((unsigned char)*buf, locale)) return (NULL); len = 3; for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, locale); buf++){ i *= 10; i += *buf - '0'; len--; } if (i < 1 || i > 366) return (NULL); tm->tm_yday = i - 1; flags |= FLAG_YDAY; break; case 'M': case 'S': if (*buf == 0 || isspace_l((unsigned char)*buf, locale)) break; if (!isdigit_l((unsigned char)*buf, locale)) return (NULL); len = 2; for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, locale); buf++){ i *= 10; i += *buf - '0'; len--; } if (c == 'M') { if (i > 59) return (NULL); tm->tm_min = i; } else { if (i > 60) return (NULL); tm->tm_sec = i; } break; case 'H': case 'I': case 'k': case 'l': /* - * Of these, %l is the only specifier explicitly - * documented as not being zero-padded. However, - * there is no harm in allowing zero-padding. + * %k and %l specifiers are documented as being + * blank-padded. However, there is no harm in + * allowing zero-padding. * - * XXX The %l specifier may gobble one too many + * XXX %k and %l specifiers may gobble one too many * digits if used incorrectly. */ + + len = 2; + if ((c == 'k' || c == 'l') && + isblank_l((unsigned char)*buf, locale)) { + buf++; + len = 1; + } + if (!isdigit_l((unsigned char)*buf, locale)) return (NULL); - len = 2; for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, locale); buf++) { i *= 10; i += *buf - '0'; len--; } if (c == 'H' || c == 'k') { if (i > 23) return (NULL); } else if (i == 0 || i > 12) return (NULL); tm->tm_hour = i; break; case 'p': /* * XXX This is bogus if parsed before hour-related * specifiers. */ if (tm->tm_hour > 12) return (NULL); len = strlen(tptr->am); if (strncasecmp_l(buf, tptr->am, len, locale) == 0) { if (tm->tm_hour == 12) tm->tm_hour = 0; buf += len; break; } len = strlen(tptr->pm); if (strncasecmp_l(buf, tptr->pm, len, locale) == 0) { if (tm->tm_hour != 12) tm->tm_hour += 12; buf += len; break; } return (NULL); case 'A': case 'a': for (i = 0; i < asizeof(tptr->weekday); i++) { len = strlen(tptr->weekday[i]); if (strncasecmp_l(buf, tptr->weekday[i], len, locale) == 0) break; len = strlen(tptr->wday[i]); if (strncasecmp_l(buf, tptr->wday[i], len, locale) == 0) break; } if (i == asizeof(tptr->weekday)) return (NULL); buf += len; tm->tm_wday = i; flags |= FLAG_WDAY; break; case 'U': case 'W': /* * XXX This is bogus, as we can not assume any valid * information present in the tm structure at this * point to calculate a real value, so just check the * range for now. */ if (!isdigit_l((unsigned char)*buf, locale)) return (NULL); len = 2; for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, locale); buf++) { i *= 10; i += *buf - '0'; len--; } if (i > 53) return (NULL); if (c == 'U') day_offset = TM_SUNDAY; else day_offset = TM_MONDAY; week_offset = i; break; case 'u': case 'w': if (!isdigit_l((unsigned char)*buf, locale)) return (NULL); i = *buf++ - '0'; if (i < 0 || i > 7 || (c == 'u' && i < 1) || (c == 'w' && i > 6)) return (NULL); tm->tm_wday = i % 7; flags |= FLAG_WDAY; break; case 'e': /* * With %e format, our strftime(3) adds a blank space * before single digits. */ if (*buf != 0 && isspace_l((unsigned char)*buf, locale)) buf++; /* FALLTHROUGH */ case 'd': /* * The %e specifier was once explicitly documented as * not being zero-padded but was later changed to * equivalent to %d. There is no harm in allowing * such padding. * * XXX The %e specifier may gobble one too many * digits if used incorrectly. */ if (!isdigit_l((unsigned char)*buf, locale)) return (NULL); len = 2; for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, locale); buf++) { i *= 10; i += *buf - '0'; len--; } if (i == 0 || i > 31) return (NULL); tm->tm_mday = i; flags |= FLAG_MDAY; break; case 'B': case 'b': case 'h': for (i = 0; i < asizeof(tptr->month); i++) { if (Oalternative) { if (c == 'B') { len = strlen(tptr->alt_month[i]); if (strncasecmp_l(buf, tptr->alt_month[i], len, locale) == 0) break; } } else { len = strlen(tptr->month[i]); if (strncasecmp_l(buf, tptr->month[i], len, locale) == 0) break; } } /* * Try the abbreviated month name if the full name * wasn't found and Oalternative was not requested. */ if (i == asizeof(tptr->month) && !Oalternative) { for (i = 0; i < asizeof(tptr->month); i++) { len = strlen(tptr->mon[i]); if (strncasecmp_l(buf, tptr->mon[i], len, locale) == 0) break; } } if (i == asizeof(tptr->month)) return (NULL); tm->tm_mon = i; buf += len; flags |= FLAG_MONTH; break; case 'm': if (!isdigit_l((unsigned char)*buf, locale)) return (NULL); len = 2; for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, locale); buf++) { i *= 10; i += *buf - '0'; len--; } if (i < 1 || i > 12) return (NULL); tm->tm_mon = i - 1; flags |= FLAG_MONTH; break; case 's': { char *cp; int sverrno; long n; time_t t; sverrno = errno; errno = 0; n = strtol_l(buf, &cp, 10, locale); if (errno == ERANGE || (long)(t = n) != n) { errno = sverrno; return (NULL); } errno = sverrno; buf = cp; if (gmtime_r(&t, tm) == NULL) return (NULL); *GMTp = 1; flags |= FLAG_YDAY | FLAG_WDAY | FLAG_MONTH | FLAG_MDAY | FLAG_YEAR; } break; case 'Y': case 'y': if (*buf == 0 || isspace_l((unsigned char)*buf, locale)) break; if (!isdigit_l((unsigned char)*buf, locale)) return (NULL); len = (c == 'Y') ? 4 : 2; for (i = 0; len && *buf != 0 && isdigit_l((unsigned char)*buf, locale); buf++) { i *= 10; i += *buf - '0'; len--; } if (c == 'Y') century = i / 100; year = i % 100; flags |= FLAG_YEAR; break; case 'Z': { const char *cp; char *zonestr; for (cp = buf; *cp && isupper_l((unsigned char)*cp, locale); ++cp) { /*empty*/} if (cp - buf) { zonestr = alloca(cp - buf + 1); strncpy(zonestr, buf, cp - buf); zonestr[cp - buf] = '\0'; tzset(); if (0 == strcmp(zonestr, "GMT") || 0 == strcmp(zonestr, "UTC")) { *GMTp = 1; } else if (0 == strcmp(zonestr, tzname[0])) { tm->tm_isdst = 0; } else if (0 == strcmp(zonestr, tzname[1])) { tm->tm_isdst = 1; } else { return (NULL); } buf += cp - buf; } } break; case 'z': { int sign = 1; if (*buf != '+') { if (*buf == '-') sign = -1; else return (NULL); } buf++; i = 0; for (len = 4; len > 0; len--) { if (isdigit_l((unsigned char)*buf, locale)) { i *= 10; i += *buf - '0'; buf++; } else if (len == 2) { i *= 100; break; } else return (NULL); } if (i > 1400 || (sign == -1 && i > 1200) || (i % 100) >= 60) return (NULL); tm->tm_hour -= sign * (i / 100); tm->tm_min -= sign * (i % 100); *GMTp = 1; } break; case 'n': case 't': while (isspace_l((unsigned char)*buf, locale)) buf++; break; default: return (NULL); } } if (century != -1 || year != -1) { if (year == -1) year = 0; if (century == -1) { if (year < 69) year += 100; } else year += century * 100 - TM_YEAR_BASE; tm->tm_year = year; } if (!(flags & FLAG_YDAY) && (flags & FLAG_YEAR)) { if ((flags & (FLAG_MONTH | FLAG_MDAY)) == (FLAG_MONTH | FLAG_MDAY)) { tm->tm_yday = start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)][tm->tm_mon] + (tm->tm_mday - 1); flags |= FLAG_YDAY; } else if (day_offset != -1) { int tmpwday, tmpyday, fwo; fwo = first_wday_of(tm->tm_year + TM_YEAR_BASE); /* No incomplete week (week 0). */ if (week_offset == 0 && fwo == day_offset) return (NULL); /* Set the date to the first Sunday (or Monday) * of the specified week of the year. */ tmpwday = (flags & FLAG_WDAY) ? tm->tm_wday : day_offset; tmpyday = (7 - fwo + day_offset) % 7 + (week_offset - 1) * 7 + (tmpwday - day_offset + 7) % 7; /* Impossible yday for incomplete week (week 0). */ if (tmpyday < 0) { if (flags & FLAG_WDAY) return (NULL); tmpyday = 0; } tm->tm_yday = tmpyday; flags |= FLAG_YDAY; } } if ((flags & (FLAG_YEAR | FLAG_YDAY)) == (FLAG_YEAR | FLAG_YDAY)) { if (!(flags & FLAG_MONTH)) { i = 0; while (tm->tm_yday >= start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)][i]) i++; if (i > 12) { i = 1; tm->tm_yday -= start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)][12]; tm->tm_year++; } tm->tm_mon = i - 1; flags |= FLAG_MONTH; } if (!(flags & FLAG_MDAY)) { tm->tm_mday = tm->tm_yday - start_of_month[isleap(tm->tm_year + TM_YEAR_BASE)] [tm->tm_mon] + 1; flags |= FLAG_MDAY; } if (!(flags & FLAG_WDAY)) { i = 0; wday_offset = first_wday_of(tm->tm_year); while (i++ <= tm->tm_yday) { if (wday_offset++ >= 6) wday_offset = 0; } tm->tm_wday = wday_offset; flags |= FLAG_WDAY; } } return ((char *)buf); } char * strptime_l(const char * __restrict buf, const char * __restrict fmt, struct tm * __restrict tm, locale_t loc) { char *ret; int gmt; FIX_LOCALE(loc); gmt = 0; ret = _strptime(buf, fmt, tm, &gmt, loc); if (ret && gmt) { time_t t = timegm(tm); localtime_r(&t, tm); } return (ret); } char * strptime(const char * __restrict buf, const char * __restrict fmt, struct tm * __restrict tm) { return strptime_l(buf, fmt, tm, __get_locale()); }