Skip to content

Commit 5649b70

Browse files
committed
Use musl's strptime implementation
This seems to save on code size compared the JS version as well as making our Wasm files more standalone. I measured the combined size of `core2.test_strptime_days` (with closure compiler enabled) and got the following results: - before: 16777 bytes - after: 17235 bytes As part of this I updated musl's implementation to support several glibc features that out current tests depend on: - Add support for '%z' for parsing timezone offsets. - Add support for '%U' and '%W' (week of the year) - Derive and set tm_yday/tm_mday/tm_yday fields where possible. Regarding '%U' and '%W' it seems this is missing from both musl and bionic: - https://github.com/aosp-mirror/platform_bionic/blob/4cfb2bde90fcf0a1f835e2b670b00e9f226c1c7c/libc/tzcode/strptime.c#L364-L375 - https://github.com/bminor/musl/blob/f314e133929b6379eccc632bef32eaebb66a7335/src/time/strptime.c#L126-L132 However, I implemented here (based on code from FreeBSD) in order to avoid any regressions (since we do have test coverage for these flags). Fixes: #21024
1 parent b3c2567 commit 5649b70

File tree

4 files changed

+385
-264
lines changed

4 files changed

+385
-264
lines changed

ChangeLog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.63 (in development)
2222
-----------------------
23+
- Emscripten now uses `strptime` from musl rather than using a custom
24+
JavaScript implementation. (#21379)
2325

2426
3.1.62 - 07/02/24
2527
-----------------

src/library.js

Lines changed: 0 additions & 264 deletions
Original file line numberDiff line numberDiff line change
@@ -728,270 +728,6 @@ addToLibrary({
728728
return newDate;
729729
},
730730

731-
strptime__deps: ['$isLeapYear', '$arraySum', '$addDays', '$MONTH_DAYS_REGULAR', '$MONTH_DAYS_LEAP',
732-
'$jstoi_q', '$intArrayFromString' ],
733-
strptime: (buf, format, tm) => {
734-
// char *strptime(const char *restrict buf, const char *restrict format, struct tm *restrict tm);
735-
// http://pubs.opengroup.org/onlinepubs/009695399/functions/strptime.html
736-
var pattern = UTF8ToString(format);
737-
738-
// escape special characters
739-
// TODO: not sure we really need to escape all of these in JS regexps
740-
var SPECIAL_CHARS = '\\!@#$^&*()+=-[]/{}|:<>?,.';
741-
for (var i=0, ii=SPECIAL_CHARS.length; i<ii; ++i) {
742-
pattern = pattern.replace(new RegExp('\\'+SPECIAL_CHARS[i], 'g'), '\\'+SPECIAL_CHARS[i]);
743-
}
744-
745-
// reduce number of matchers
746-
var EQUIVALENT_MATCHERS = {
747-
'A': '%a',
748-
'B': '%b',
749-
'c': '%a %b %d %H:%M:%S %Y',
750-
'D': '%m\\/%d\\/%y',
751-
'e': '%d',
752-
'F': '%Y-%m-%d',
753-
'h': '%b',
754-
'R': '%H\\:%M',
755-
'r': '%I\\:%M\\:%S\\s%p',
756-
'T': '%H\\:%M\\:%S',
757-
'x': '%m\\/%d\\/(?:%y|%Y)',
758-
'X': '%H\\:%M\\:%S'
759-
};
760-
// TODO: take care of locale
761-
762-
var DATE_PATTERNS = {
763-
/* weekday name */ 'a': '(?:Sun(?:day)?)|(?:Mon(?:day)?)|(?:Tue(?:sday)?)|(?:Wed(?:nesday)?)|(?:Thu(?:rsday)?)|(?:Fri(?:day)?)|(?:Sat(?:urday)?)',
764-
/* month name */ 'b': '(?:Jan(?:uary)?)|(?:Feb(?:ruary)?)|(?:Mar(?:ch)?)|(?:Apr(?:il)?)|May|(?:Jun(?:e)?)|(?:Jul(?:y)?)|(?:Aug(?:ust)?)|(?:Sep(?:tember)?)|(?:Oct(?:ober)?)|(?:Nov(?:ember)?)|(?:Dec(?:ember)?)',
765-
/* century */ 'C': '\\d\\d',
766-
/* day of month */ 'd': '0[1-9]|[1-9](?!\\d)|1\\d|2\\d|30|31',
767-
/* hour (24hr) */ 'H': '\\d(?!\\d)|[0,1]\\d|20|21|22|23',
768-
/* hour (12hr) */ 'I': '\\d(?!\\d)|0\\d|10|11|12',
769-
/* day of year */ 'j': '00[1-9]|0?[1-9](?!\\d)|0?[1-9]\\d(?!\\d)|[1,2]\\d\\d|3[0-6]\\d',
770-
/* month */ 'm': '0[1-9]|[1-9](?!\\d)|10|11|12',
771-
/* minutes */ 'M': '0\\d|\\d(?!\\d)|[1-5]\\d',
772-
/* whitespace */ 'n': ' ',
773-
/* AM/PM */ 'p': 'AM|am|PM|pm|A\\.M\\.|a\\.m\\.|P\\.M\\.|p\\.m\\.',
774-
/* seconds */ 'S': '0\\d|\\d(?!\\d)|[1-5]\\d|60',
775-
/* week number */ 'U': '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53',
776-
/* week number */ 'W': '0\\d|\\d(?!\\d)|[1-4]\\d|50|51|52|53',
777-
/* weekday number */ 'w': '[0-6]',
778-
/* 2-digit year */ 'y': '\\d\\d',
779-
/* 4-digit year */ 'Y': '\\d\\d\\d\\d',
780-
/* whitespace */ 't': ' ',
781-
/* time zone */ 'z': 'Z|(?:[\\+\\-]\\d\\d:?(?:\\d\\d)?)'
782-
};
783-
784-
var MONTH_NUMBERS = {JAN: 0, FEB: 1, MAR: 2, APR: 3, MAY: 4, JUN: 5, JUL: 6, AUG: 7, SEP: 8, OCT: 9, NOV: 10, DEC: 11};
785-
var DAY_NUMBERS_SUN_FIRST = {SUN: 0, MON: 1, TUE: 2, WED: 3, THU: 4, FRI: 5, SAT: 6};
786-
var DAY_NUMBERS_MON_FIRST = {MON: 0, TUE: 1, WED: 2, THU: 3, FRI: 4, SAT: 5, SUN: 6};
787-
788-
var capture = [];
789-
var pattern_out = pattern
790-
.replace(/%(.)/g, (m, c) => EQUIVALENT_MATCHERS[c] || m)
791-
.replace(/%(.)/g, (_, c) => {
792-
let pat = DATE_PATTERNS[c];
793-
if (pat){
794-
capture.push(c);
795-
return `(${pat})`;
796-
} else {
797-
return c;
798-
}
799-
})
800-
.replace( // any number of space or tab characters match zero or more spaces
801-
/\s+/g,'\\s*'
802-
);
803-
804-
var matches = new RegExp('^'+pattern_out, "i").exec(UTF8ToString(buf))
805-
806-
function initDate() {
807-
function fixup(value, min, max) {
808-
return (typeof value != 'number' || isNaN(value)) ? min : (value>=min ? (value<=max ? value: max): min);
809-
};
810-
return {
811-
year: fixup({{{ makeGetValue('tm', C_STRUCTS.tm.tm_year, 'i32') }}} + 1900 , 1970, 9999),
812-
month: fixup({{{ makeGetValue('tm', C_STRUCTS.tm.tm_mon, 'i32') }}}, 0, 11),
813-
day: fixup({{{ makeGetValue('tm', C_STRUCTS.tm.tm_mday, 'i32') }}}, 1, 31),
814-
hour: fixup({{{ makeGetValue('tm', C_STRUCTS.tm.tm_hour, 'i32') }}}, 0, 23),
815-
min: fixup({{{ makeGetValue('tm', C_STRUCTS.tm.tm_min, 'i32') }}}, 0, 59),
816-
sec: fixup({{{ makeGetValue('tm', C_STRUCTS.tm.tm_sec, 'i32') }}}, 0, 59),
817-
gmtoff: 0
818-
};
819-
};
820-
821-
if (matches) {
822-
var date = initDate();
823-
var value;
824-
825-
var getMatch = (symbol) => {
826-
var pos = capture.indexOf(symbol);
827-
// check if symbol appears in regexp
828-
if (pos >= 0) {
829-
// return matched value or null (falsy!) for non-matches
830-
return matches[pos+1];
831-
}
832-
return;
833-
};
834-
835-
// seconds
836-
if ((value=getMatch('S'))) {
837-
date.sec = jstoi_q(value);
838-
}
839-
840-
// minutes
841-
if ((value=getMatch('M'))) {
842-
date.min = jstoi_q(value);
843-
}
844-
845-
// hours
846-
if ((value=getMatch('H'))) {
847-
// 24h clock
848-
date.hour = jstoi_q(value);
849-
} else if ((value = getMatch('I'))) {
850-
// AM/PM clock
851-
var hour = jstoi_q(value);
852-
if ((value=getMatch('p'))) {
853-
hour += value.toUpperCase()[0] === 'P' ? 12 : 0;
854-
}
855-
date.hour = hour;
856-
}
857-
858-
// year
859-
if ((value=getMatch('Y'))) {
860-
// parse from four-digit year
861-
date.year = jstoi_q(value);
862-
} else if ((value=getMatch('y'))) {
863-
// parse from two-digit year...
864-
var year = jstoi_q(value);
865-
if ((value=getMatch('C'))) {
866-
// ...and century
867-
year += jstoi_q(value)*100;
868-
} else {
869-
// ...and rule-of-thumb
870-
year += year<69 ? 2000 : 1900;
871-
}
872-
date.year = year;
873-
}
874-
875-
// month
876-
if ((value=getMatch('m'))) {
877-
// parse from month number
878-
date.month = jstoi_q(value)-1;
879-
} else if ((value=getMatch('b'))) {
880-
// parse from month name
881-
date.month = MONTH_NUMBERS[value.substring(0,3).toUpperCase()] || 0;
882-
// TODO: derive month from day in year+year, week number+day of week+year
883-
}
884-
885-
// day
886-
if ((value=getMatch('d'))) {
887-
// get day of month directly
888-
date.day = jstoi_q(value);
889-
} else if ((value=getMatch('j'))) {
890-
// get day of month from day of year ...
891-
var day = jstoi_q(value);
892-
var leapYear = isLeapYear(date.year);
893-
for (var month=0; month<12; ++month) {
894-
var daysUntilMonth = arraySum(leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, month-1);
895-
if (day<=daysUntilMonth+(leapYear ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR)[month]) {
896-
date.day = day-daysUntilMonth;
897-
}
898-
}
899-
} else if ((value=getMatch('a'))) {
900-
// get day of month from weekday ...
901-
var weekDay = value.substring(0,3).toUpperCase();
902-
if ((value=getMatch('U'))) {
903-
// ... and week number (Sunday being first day of week)
904-
// Week number of the year (Sunday as the first day of the week) as a decimal number [00,53].
905-
// All days in a new year preceding the first Sunday are considered to be in week 0.
906-
var weekDayNumber = DAY_NUMBERS_SUN_FIRST[weekDay];
907-
var weekNumber = jstoi_q(value);
908-
909-
// January 1st
910-
var janFirst = new Date(date.year, 0, 1);
911-
var endDate;
912-
if (janFirst.getDay() === 0) {
913-
// Jan 1st is a Sunday, and, hence in the 1st CW
914-
endDate = addDays(janFirst, weekDayNumber+7*(weekNumber-1));
915-
} else {
916-
// Jan 1st is not a Sunday, and, hence still in the 0th CW
917-
endDate = addDays(janFirst, 7-janFirst.getDay()+weekDayNumber+7*(weekNumber-1));
918-
}
919-
date.day = endDate.getDate();
920-
date.month = endDate.getMonth();
921-
} else if ((value=getMatch('W'))) {
922-
// ... and week number (Monday being first day of week)
923-
// Week number of the year (Monday as the first day of the week) as a decimal number [00,53].
924-
// All days in a new year preceding the first Monday are considered to be in week 0.
925-
var weekDayNumber = DAY_NUMBERS_MON_FIRST[weekDay];
926-
var weekNumber = jstoi_q(value);
927-
928-
// January 1st
929-
var janFirst = new Date(date.year, 0, 1);
930-
var endDate;
931-
if (janFirst.getDay()===1) {
932-
// Jan 1st is a Monday, and, hence in the 1st CW
933-
endDate = addDays(janFirst, weekDayNumber+7*(weekNumber-1));
934-
} else {
935-
// Jan 1st is not a Monday, and, hence still in the 0th CW
936-
endDate = addDays(janFirst, 7-janFirst.getDay()+1+weekDayNumber+7*(weekNumber-1));
937-
}
938-
939-
date.day = endDate.getDate();
940-
date.month = endDate.getMonth();
941-
}
942-
}
943-
944-
// time zone
945-
if ((value = getMatch('z'))) {
946-
// GMT offset as either 'Z' or +-HH:MM or +-HH or +-HHMM
947-
if (value.toLowerCase() === 'z'){
948-
date.gmtoff = 0;
949-
} else {
950-
var match = value.match(/^((?:\-|\+)\d\d):?(\d\d)?/);
951-
date.gmtoff = match[1] * 3600;
952-
if (match[2]) {
953-
date.gmtoff += date.gmtoff >0 ? match[2] * 60 : -match[2] * 60
954-
}
955-
}
956-
}
957-
958-
/*
959-
tm_sec int seconds after the minute 0-61*
960-
tm_min int minutes after the hour 0-59
961-
tm_hour int hours since midnight 0-23
962-
tm_mday int day of the month 1-31
963-
tm_mon int months since January 0-11
964-
tm_year int years since 1900
965-
tm_wday int days since Sunday 0-6
966-
tm_yday int days since January 1 0-365
967-
tm_isdst int Daylight Saving Time flag
968-
tm_gmtoff long offset from GMT (seconds)
969-
*/
970-
971-
var fullDate = new Date(date.year, date.month, date.day, date.hour, date.min, date.sec, 0);
972-
{{{ makeSetValue('tm', C_STRUCTS.tm.tm_sec, 'fullDate.getSeconds()', 'i32') }}};
973-
{{{ makeSetValue('tm', C_STRUCTS.tm.tm_min, 'fullDate.getMinutes()', 'i32') }}};
974-
{{{ makeSetValue('tm', C_STRUCTS.tm.tm_hour, 'fullDate.getHours()', 'i32') }}};
975-
{{{ makeSetValue('tm', C_STRUCTS.tm.tm_mday, 'fullDate.getDate()', 'i32') }}};
976-
{{{ makeSetValue('tm', C_STRUCTS.tm.tm_mon, 'fullDate.getMonth()', 'i32') }}};
977-
{{{ makeSetValue('tm', C_STRUCTS.tm.tm_year, 'fullDate.getFullYear()-1900', 'i32') }}};
978-
{{{ makeSetValue('tm', C_STRUCTS.tm.tm_wday, 'fullDate.getDay()', 'i32') }}};
979-
{{{ makeSetValue('tm', C_STRUCTS.tm.tm_yday, 'arraySum(isLeapYear(fullDate.getFullYear()) ? MONTH_DAYS_LEAP : MONTH_DAYS_REGULAR, fullDate.getMonth()-1)+fullDate.getDate()-1', 'i32') }}};
980-
{{{ makeSetValue('tm', C_STRUCTS.tm.tm_isdst, '0', 'i32') }}};
981-
{{{ makeSetValue('tm', C_STRUCTS.tm.tm_gmtoff, 'date.gmtoff', LONG_TYPE) }}};
982-
983-
// we need to convert the matched sequence into an integer array to take care of UTF-8 characters > 0x7F
984-
// TODO: not sure that intArrayFromString handles all unicode characters correctly
985-
return buf+intArrayFromString(matches[0]).length-1;
986-
}
987-
988-
return 0;
989-
},
990-
strptime_l__deps: ['strptime'],
991-
strptime_l: (buf, format, tm, locale) => {
992-
return _strptime(buf, format, tm); // no locale support yet
993-
},
994-
995731
// ==========================================================================
996732
// setjmp.h
997733
// ==========================================================================

0 commit comments

Comments
 (0)