blob: 44c6a942843702122bdf01d1f4a832c6ea45f31a [file] [log] [blame]
/*****************************************************************************\
* src/common/parse_time.c - time parsing utility functions
*****************************************************************************
* Copyright (C) 2005-2006 The Regents of the University of California.
* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
* Written by Morris Jette <jette1@llnl.gov>.
* CODE-OCEC-09-009. All rights reserved.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm 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 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include "config.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include "slurm/slurm.h"
#include "src/common/macros.h"
#include "src/common/slurm_time.h"
#include "src/common/strlcpy.h"
#include "src/common/xstring.h"
#include "src/common/parse_time.h"
/*
** Define slurm-specific aliases for use by plugins, see slurm_xlator.h
** for details.
*/
strong_alias(parse_time, slurm_parse_time);
strong_alias(parse_time_make_str_utc, slurm_parse_time_make_str_utc);
//slurm_make_time_str is already exported
strong_alias(time_str2mins, slurm_time_str2mins);
strong_alias(time_str2secs, slurm_time_str2secs);
strong_alias(secs2time_str, slurm_secs2time_str);
strong_alias(mins2time_str, slurm_mins2time_str);
typedef struct unit_names {
char *name;
int name_len;
int multiplier;
} unit_names_t;
static unit_names_t un[] = {
{"seconds", 7, 1},
{"second", 6, 1},
{"minutes", 7, 60},
{"minute", 6, 60},
{"hours", 5, (60*60)},
{"hour", 4, (60*60)},
{"days", 4, (24*60*60)},
{"day", 3, (24*60*60)},
{"weeks", 5, (7*24*60*60)},
{"week", 4, (7*24*60*60)},
{NULL, 0, 0}
};
/* _is_valid_timespec()
*
* Validate that time format follows
* is supported.
*/
static bool
_is_valid_timespec(const char *s)
{
int digit;
int dash;
int colon;
bool already_digit = false;
digit = dash = colon = 0;
while (*s) {
if (*s >= '0' && *s <= '9') {
if (!already_digit) {
++digit;
already_digit = true;
}
} else if (*s == '-') {
already_digit = false;
++dash;
if (colon)
return false;
} else if (*s == ':') {
already_digit = false;
++colon;
} else {
return false;
}
++s;
}
if (!digit)
return false;
if (dash > 1
|| colon > 2)
return false;
if (dash) {
if (colon == 1
&& digit < 3)
return false;
if (colon == 2
&& digit < 4)
return false;
} else {
if (colon == 1
&& digit < 2)
return false;
if (colon == 2
&& digit < 3)
return false;
}
return true;
}
/* convert time differential string into a number of seconds
* time_str (in): string to parse
* pos (in/out): position of parse start/end
* delta (out): delta in seconds
* RET: -1 on error, 0 otherwise
*/
static int _get_delta(const char *time_str, int *pos, long *delta)
{
int i, offset;
long cnt = 0;
int digit = 0;
for (offset = (*pos) + 1;
((time_str[offset] != '\0') && (time_str[offset] != '\n'));
offset++) {
if (isspace((int)time_str[offset]))
continue;
for (i=0; un[i].name; i++) {
if (!xstrncasecmp((time_str + offset),
un[i].name, un[i].name_len)) {
offset += un[i].name_len;
cnt *= un[i].multiplier;
break;
}
}
if (un[i].name)
break; /* processed unit name */
if ((time_str[offset] >= '0') && (time_str[offset] <= '9')) {
cnt = (cnt * 10) + (time_str[offset] - '0');
digit++;
continue;
}
goto prob;
}
if (!digit) /* No numbers after the '=' */
return -1;
*pos = offset - 1;
*delta = cnt;
return 0;
prob: *pos = offset - 1;
return -1;
}
/* convert "HH:MM[:SS] [AM|PM]" string to numeric values
* time_str (in): string to parse
* pos (in/out): position of parse start/end
* hour, minute, second (out): numeric values
* RET: -1 on error, 0 otherwise
*/
static int _get_time(const char *time_str, int *pos, int *hour, int *minute,
int *second)
{
int hr, min, sec;
int offset = *pos;
/* get hour */
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
hr = time_str[offset++] - '0';
if (time_str[offset] != ':') {
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
hr = (hr * 10) + time_str[offset++] - '0';
}
if (hr > 23) {
offset -= 2;
goto prob;
}
if (time_str[offset] != ':')
goto prob;
offset++;
/* get minute */
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
min = time_str[offset++] - '0';
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
min = (min * 10) + time_str[offset++] - '0';
if (min > 59) {
offset -= 2;
goto prob;
}
/* get optional second */
if (time_str[offset] == ':') {
offset++;
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
sec = time_str[offset++] - '0';
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
sec = (sec * 10) + time_str[offset++] - '0';
if (sec > 59) {
offset -= 2;
goto prob;
}
} else
sec = 0;
while (isspace((int)time_str[offset])) {
offset++;
}
if (!xstrncasecmp(time_str + offset, "pm", 2)) {
hr += 12;
if (hr > 23) {
if (hr == 24)
hr = 12;
else
goto prob;
}
offset += 2;
} else if (!xstrncasecmp(time_str + offset, "am", 2)) {
if (hr > 11) {
if (hr == 12)
hr = 0;
else
goto prob;
}
offset += 2;
}
*pos = offset - 1;
*hour = hr;
*minute = min;
*second = sec;
return 0;
prob: *pos = offset;
return -1;
}
/* convert "MMDDYY" "MM.DD.YY" or "MM/DD/YY" string to numeric values
* or "YYYY-MM-DD string to numeric values
* time_str (in): string to parse
* pos (in/out): position of parse start/end
* month, mday, year (out): numeric values
* RET: -1 on error, 0 otherwise
*/
static int _get_date(const char *time_str, int *pos, int *month, int *mday,
int *year)
{
int mon, day, yr;
int offset = *pos;
int len;
if (!time_str)
goto prob;
len = strlen(time_str);
if ((len >= (offset+7)) && (time_str[offset+4] == '-')
&& (time_str[offset+7] == '-')) {
/* get year */
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
yr = time_str[offset++] - '0';
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
yr = (yr * 10) + time_str[offset++] - '0';
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
yr = (yr * 10) + time_str[offset++] - '0';
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
yr = (yr * 10) + time_str[offset++] - '0';
offset++; // for the -
/* get month */
mon = time_str[offset++] - '0';
if ((time_str[offset] >= '0') && (time_str[offset] <= '9'))
mon = (mon * 10) + time_str[offset++] - '0';
if ((mon < 1) || (mon > 12)) {
offset -= 2;
goto prob;
}
offset++; // for the -
/* get day */
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
day = time_str[offset++] - '0';
if ((time_str[offset] >= '0') && (time_str[offset] <= '9'))
day = (day * 10) + time_str[offset++] - '0';
if ((day < 1) || (day > 31)) {
offset -= 2;
goto prob;
}
*pos = offset - 1;
*month = mon - 1; /* zero origin */
*mday = day;
*year = yr - 1900; /* need to make it slurm_mktime
happy 1900 == "00" */
return 0;
}
/* get month */
mon = time_str[offset++] - '0';
if ((time_str[offset] >= '0') && (time_str[offset] <= '9'))
mon = (mon * 10) + time_str[offset++] - '0';
if ((mon < 1) || (mon > 12)) {
offset -= 2;
goto prob;
}
if ((time_str[offset] == '.') || (time_str[offset] == '/'))
offset++;
/* get day */
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
day = time_str[offset++] - '0';
if ((time_str[offset] >= '0') && (time_str[offset] <= '9'))
day = (day * 10) + time_str[offset++] - '0';
if ((day < 1) || (day > 31)) {
offset -= 2;
goto prob;
}
if ((time_str[offset] == '.') || (time_str[offset] == '/'))
offset++;
/* get optional year */
if ((time_str[offset] >= '0') && (time_str[offset] <= '9')) {
yr = time_str[offset++] - '0';
if ((time_str[offset] < '0') || (time_str[offset] > '9'))
goto prob;
yr = (yr * 10) + time_str[offset++] - '0';
} else
yr = 0;
*pos = offset - 1;
*month = mon - 1; /* zero origin */
*mday = day;
if (yr)
*year = yr + 100; /* 1900 == "00" */
return 0;
prob: *pos = offset;
return -1;
}
/* Convert string to equivalent time value
* input formats:
* today or tomorrow
* midnight, noon, elevenses (11 AM), fika (3 PM), teatime (4 PM)
* HH:MM[:SS] [AM|PM]
* MMDD[YY] or MM/DD[/YY] or MM.DD[.YY]
* MM/DD[/YY]-HH:MM[:SS]
* YYYY-MM-DD[THH:MM[:SS]]
* now[{+|-}count[seconds(default)|minutes|hours|days|weeks]]
*
* Invalid input results in message to stderr and return value of zero
* NOTE: not thread safe
* NOTE: by default this will look into the future for the next time.
* if you want to look in the past set the past flag.
*/
extern time_t parse_time(const char *time_str, int past)
{
time_t time_now;
struct tm time_now_tm;
int hour = -1, minute = -1, second = 0;
int month = -1, mday = -1, year = -1;
int pos = 0;
struct tm res_tm;
time_t ret_time;
if (!xstrncasecmp(time_str, "uts", 3)) {
char *last = NULL;
long uts = strtol(time_str+3, &last, 10);
if ((uts < 1000000) || (uts == LONG_MAX) ||
(last == NULL) || (last[0] != '\0'))
goto prob;
return (time_t) uts;
}
time_now = time(NULL);
localtime_r(&time_now, &time_now_tm);
for (pos=0; ((time_str[pos] != '\0') && (time_str[pos] != '\n'));
pos++) {
if (isblank((int)time_str[pos]) ||
(time_str[pos] == '-') || (time_str[pos] == 'T'))
continue;
if (!xstrncasecmp(time_str + pos, "today", 5)) {
month = time_now_tm.tm_mon;
mday = time_now_tm.tm_mday;
year = time_now_tm.tm_year;
pos += 4;
continue;
}
if (!xstrncasecmp(time_str + pos, "tomorrow", 8)) {
time_t later = time_now + (24 * 60 * 60);
struct tm later_tm;
localtime_r(&later, &later_tm);
month = later_tm.tm_mon;
mday = later_tm.tm_mday;
year = later_tm.tm_year;
pos += 7;
continue;
}
if (!xstrncasecmp(time_str + pos, "midnight", 8)) {
hour = 0;
minute = 0;
second = 0;
pos += 7;
continue;
}
if (!xstrncasecmp(time_str + pos, "noon", 4)) {
hour = 12;
minute = 0;
second = 0;
pos += 3;
continue;
}
if (!xstrncasecmp(time_str + pos, "elevenses", 9)) {
hour = 11;
minute = 0;
second = 0;
pos += 8;
continue;
}
if (!xstrncasecmp(time_str + pos, "fika", 4)) {
hour = 15;
minute = 0;
second = 0;
pos += 3;
continue;
}
if (!xstrncasecmp(time_str + pos, "teatime", 7)) {
hour = 16;
minute = 0;
second = 0;
pos += 6;
continue;
}
if (!xstrncasecmp(time_str+pos, "now", 3)) {
int i;
long delta = 0;
time_t later;
struct tm later_tm;
for (i=(pos+3); ; i++) {
if (time_str[i] == '+') {
pos += i;
if (_get_delta(time_str, &pos, &delta))
goto prob;
break;
}
if (time_str[i] == '-') {
pos += i;
if (_get_delta(time_str, &pos, &delta))
goto prob;
delta = -delta;
break;
}
if (isblank((int)time_str[i]))
continue;
if ((time_str[i] == '\0')
|| (time_str[i] == '\n')) {
pos += (i-1);
break;
}
pos += i;
goto prob;
}
later = time_now + delta;
localtime_r(&later, &later_tm);
month = later_tm.tm_mon;
mday = later_tm.tm_mday;
year = later_tm.tm_year;
hour = later_tm.tm_hour;
minute = later_tm.tm_min;
second = later_tm.tm_sec;
continue;
}
if ((time_str[pos] < '0') || (time_str[pos] > '9'))
/* invalid */
goto prob;
/* We have some numeric value to process */
if ((time_str[pos+1] == ':') || (time_str[pos+2] == ':')) {
/* Parse the time stamp */
if (_get_time(time_str, &pos, &hour, &minute, &second))
goto prob;
continue;
}
if (_get_date(time_str, &pos, &month, &mday, &year))
goto prob;
}
/* printf("%d/%d/%d %d:%d\n",month+1,mday,year,hour+1,minute); */
if ((hour == -1) && (month == -1)) /* nothing specified, time=0 */
return (time_t) 0;
else if ((hour == -1) && (month != -1)) { /* date, no time implies 00:00 */
hour = 0;
minute = 0;
}
else if ((hour != -1) && (month == -1)) {
/* time, no date implies soonest day */
if (past || (hour > time_now_tm.tm_hour)
|| ((hour == time_now_tm.tm_hour)
&& (minute > time_now_tm.tm_min))) {
/* today */
month = time_now_tm.tm_mon;
mday = time_now_tm.tm_mday;
year = time_now_tm.tm_year;
} else {/* tomorrow */
time_t later = time_now + (24 * 60 * 60);
struct tm later_tm;
localtime_r(&later, &later_tm);
month = later_tm.tm_mon;
mday = later_tm.tm_mday;
year = later_tm.tm_year;
}
}
if (year == -1) {
if (past) {
if (month > time_now_tm.tm_mon) {
/* last year */
year = time_now_tm.tm_year - 1;
} else {
/* this year */
year = time_now_tm.tm_year;
}
} else if ((month > time_now_tm.tm_mon)
|| ((month == time_now_tm.tm_mon)
&& (mday > time_now_tm.tm_mday))
|| ((month == time_now_tm.tm_mon)
&& (mday == time_now_tm.tm_mday)
&& (hour > time_now_tm.tm_hour))
|| ((month == time_now_tm.tm_mon)
&& (mday == time_now_tm.tm_mday)
&& (hour == time_now_tm.tm_hour)
&& (minute > time_now_tm.tm_min))) {
/* this year */
year = time_now_tm.tm_year;
} else {
/* next year */
year = time_now_tm.tm_year + 1;
}
}
/* convert the time into time_t format */
memset(&res_tm, 0, sizeof(res_tm));
res_tm.tm_sec = second;
res_tm.tm_min = minute;
res_tm.tm_hour = hour;
res_tm.tm_mday = mday;
res_tm.tm_mon = month;
res_tm.tm_year = year;
/* printf("%d/%d/%d %d:%d\n",month+1,mday,year,hour,minute); */
if ((ret_time = slurm_mktime(&res_tm)) != -1)
return ret_time;
prob: fprintf(stderr, "Invalid time specification (pos=%d): %s\n", pos, time_str);
errno = ESLURM_INVALID_TIME_VALUE;
return (time_t) 0;
}
/*
* Smart date for @epoch, relative to current date.
* Maximum output length: 12 characters + '\0'
* 19 Jan 2003 (distant past or future)
* Ystday 20:13
* 12:26:38 (today)
* Tomorr 03:22
* Sat 02:17 (next Saturday)
* 18 Jun 13:14 (non-close past or future)
* 012345678901
* Uses base-10 YYYYddd numbers to compute date distances.
*/
static char *_relative_date_fmt(const struct tm *when)
{
static int todays_date;
int distance = 1000 * (when->tm_year + 1900) + when->tm_yday;
if (!todays_date) {
time_t now = time(NULL);
struct tm tm;
localtime_r(&now, &tm);
todays_date = 1000 * (tm.tm_year + 1900) + tm.tm_yday;
}
distance -= todays_date;
if (distance == -1) /* yesterday */
return "Ystday %H:%M";
if (distance == 0) /* same day */
return "%H:%M:%S";
if (distance == 1) /* tomorrow */
return "Tomorr %H:%M";
if (distance < -365 || distance > 365) /* far distance */
return "%-d %b %Y";
if (distance < -1 || distance > 6) /* medium distance */
return "%-d %b %H:%M";
return "%a %H:%M"; /* near distance */
}
static void _make_time_str_internal(time_t *time, bool utc, char *string,
int size)
{
struct tm time_tm;
if (utc)
gmtime_r(time, &time_tm);
else
localtime_r(time, &time_tm);
if ((*time == (time_t) 0) || (*time == (time_t) INFINITE)) {
snprintf(string, size, "Unknown");
} else if (*time == (time_t) NO_VAL) {
snprintf(string, size, "None");
} else {
static char fmt_buf[32];
static const char *display_fmt = "%FT%T";
if (!utc) {
char *fmt = getenv("SLURM_TIME_FORMAT");
if ((!fmt) || (!*fmt) || (!xstrcmp(fmt, "standard"))) {
;
} else if (!xstrcmp(fmt, "relative")) {
display_fmt = _relative_date_fmt(&time_tm);
} else if ((strchr(fmt, '%') == NULL) ||
(strlen(fmt) >= sizeof(fmt_buf))) {
error("invalid SLURM_TIME_FORMAT = '%s'", fmt);
} else {
strlcpy(fmt_buf, fmt, sizeof(fmt_buf));
display_fmt = fmt_buf;
}
}
/*
* The result from strftime() is undefined if the buffer is
* too small. Fill it with "#######..." instead.
*/
if (!strftime(string, size, display_fmt, &time_tm)) {
memset(string, '#', size);
string[size - 1] = 0;
}
}
}
/*
* slurm_make_time_str - convert time_t to formatted string for user output
*
* The format depends on the environment variable SLURM_TIME_FORMAT, which may
* be set to 'standard' (fallback, same as if not set), 'relative' (format is
* relative to today's date and optimized for space), or a strftime(3) string.
*
* IN time - a time stamp
* OUT string - pointer user defined buffer
* IN size - length of string buffer, we recommend a size of 32 bytes to
* easily support different site-specific formats
*/
extern void
slurm_make_time_str (time_t *time, char *string, int size)
{
_make_time_str_internal(time, false, string, size);
}
/*
* Convert time_t to fixed "%FT%T" formatted string expressed in UTC.
*
* IN time - a timestamp
* OUT string - pointer user defined buffer
* IN size - length of string buffer (recommend 32 bytes)
*/
extern void parse_time_make_str_utc(time_t *time, char *string, int size)
{
_make_time_str_internal(time, true, string, size);
}
/* Convert a string to an equivalent time value
* input formats:
* min
* min:sec
* hr:min:sec
* days-hr:min:sec
* days-hr
* output:
* minutes for time_str2mins
* seconds for time_str2secs
* NO_VAL on error
* INFINITE for "infinite" or "unlimited"
*/
extern int time_str2secs(const char *string)
{
int d = 0, h = 0, m = 0, s = 0;
if ((string == NULL) || (string[0] == '\0'))
return NO_VAL; /* invalid input */
if ((!xstrcasecmp(string, "-1"))
|| (!xstrcasecmp(string, "INFINITE"))
|| (!xstrcasecmp(string, "UNLIMITED"))) {
return INFINITE;
}
if (! _is_valid_timespec(string))
return NO_VAL;
if (xstrchr(string, '-')) {
/* days-[hours[:minutes[:seconds]]] */
sscanf(string, "%d-%d:%d:%d", &d, &h, &m, &s);
d *= 86400;
h *= 3600;
m *= 60;
} else {
if (sscanf(string, "%d:%d:%d", &h, &m, &s) == 3) {
/* hours:minutes:seconds */
h *= 3600;
m *= 60;
} else {
/*
* minutes[:seconds]
* h is minutes here and m is seconds due
* to sscanf parsing left to right
*/
s = m;
m = h * 60;
h = 0;
}
}
return (d + h + m + s);
}
extern int time_str2mins(const char *string)
{
int i = time_str2secs(string);
if ((i != INFINITE) && (i != NO_VAL))
i = ROUNDUP(i, 60); /* round up */
return i;
}
extern void secs2time_str(time_t time, char *string, int size)
{
if (time == INFINITE) {
snprintf(string, size, "UNLIMITED");
} else {
long days, hours, minutes, seconds;
seconds = time % 60;
minutes = (time / 60) % 60;
hours = (time / 3600) % 24;
days = time / 86400;
if ((days < 0) || (hours < 0) || (minutes < 0) ||
(seconds < 0)) {
snprintf(string, size, "INVALID");
} else if (days) {
snprintf(string, size,
"%ld-%2.2ld:%2.2ld:%2.2ld",
days, hours, minutes, seconds);
} else {
snprintf(string, size,
"%2.2ld:%2.2ld:%2.2ld",
hours, minutes, seconds);
}
}
}
extern void mins2time_str(uint32_t time, char *string, int size)
{
if (time == INFINITE) {
snprintf(string, size, "UNLIMITED");
} else {
long days, hours, minutes, seconds;
seconds = 0;
minutes = time % 60;
hours = time / 60 % 24;
days = time / 1440;
if ((days < 0) || (hours < 0) || (minutes < 0) ||
(seconds < 0)) {
snprintf(string, size, "INVALID");
} else if (days) {
snprintf(string, size,
"%ld-%2.2ld:%2.2ld:%2.2ld",
days, hours, minutes, seconds);
} else {
snprintf(string, size,
"%2.2ld:%2.2ld:%2.2ld",
hours, minutes, seconds);
}
}
}