Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for milliseconds formatted as %f in strptime(fmt). #1413

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/content/3.manual/manual.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1921,6 +1921,7 @@ sections:
The `strptime(fmt)` builtin parses input strings matching the
`fmt` argument. The output is in the "broken down time"
representation consumed by `gmtime` and output by `mktime`.
Milliseconds can be parsed by `strptime(fmt)` as %f in `fmt`.

The `strftime(fmt)` builtin formats a time (GMT) with the
given format. The `strflocaltime` does the same, but using
Expand Down
39 changes: 32 additions & 7 deletions src/builtin.c
Original file line number Diff line number Diff line change
Expand Up @@ -1219,24 +1219,49 @@ static jv f_strptime(jq_state *jq, jv a, jv b) {
memset(&tm, 0, sizeof(tm));
const char *input = jv_string_value(a);
const char *fmt = jv_string_value(b);
const char *end = strptime(input, fmt, &tm);

if (end == NULL || (*end != '\0' && !isspace(*end))) {
jv e = jv_invalid_with_msg(jv_string_fmt("date \"%s\" does not match format \"%s\"", input, fmt));
jv_free(a);
jv_free(b);
return e;
char *inputdup = strdup(input);
char *fmtdup = strdup(fmt);

int ms = -1;
char *pf = strstr(fmtdup, "%f");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Watch out! % can be escaped with a preceding (unescaped) %. You have to step over the format string character (well, byte) by character (byte).

if (pf) {
char *msfmtloc = pf - 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Buffer underrun if the format string starts with "%f".

char delim = *msfmtloc;
*msfmtloc = '\0';
char *msloc = strrchr(inputdup, delim);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assumption here is that delim will not be used for anything else after the %f... I'm not very comfortable with this.

if (msloc == NULL)
goto bad_fmt_out;
*msloc = '\0';
ms = atoi(msloc + 1);
}

const char *end = strptime(inputdup, fmtdup, &tm);

if (end == NULL || (*end != '\0' && !isspace(*end)))
goto bad_fmt_out;

free(inputdup);
free(fmtdup);
jv_free(b);
if (tm.tm_wday == 0 && tm.tm_yday == 0 && my_mktime(&tm) == (time_t)-2) {
jv_free(a);
return jv_invalid_with_msg(jv_string("strptime/1 not supported on this platform"));
}
jv r = tm2jv(&tm);
if (ms >= 0)
r = jv_array_append(r, jv_number(ms));
if (*end != '\0')
r = jv_array_append(r, jv_string(end));
jv_free(a); // must come after `*end` because `end` is a pointer into `a`'s string
return r;

bad_fmt_out:;
jv e = jv_invalid_with_msg(jv_string_fmt("date \"%s\" does not match format \"%s\"", input, fmt));
jv_free(a);
jv_free(b);
free(inputdup);
free(fmtdup);
return e;
}
#else
static jv f_strptime(jq_state *jq, jv a, jv b) {
Expand Down
4 changes: 3 additions & 1 deletion tests/optional.test
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ strftime("%A, %B %e, %Y")
1435677542.822351
"Tuesday, June 30, 2015"


strptime("%Y+%m-%dT%H:%M:%S.%fZ")
"2015+03-05T19:40:53.592Z"
[2015,2,5,19,40,53,4,63,592]