| /*****************************************************************************\ | |
| * 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; | |
| bool calendar_days; | |
| } unit_names_t; | |
| static unit_names_t un[] = { | |
| { "seconds", 7, 1, false }, | |
| { "second", 6, 1, false }, | |
| { "minutes", 7, 60, false }, | |
| { "minute", 6, 60, false }, | |
| { "hours", 5, (60 * 60), false }, | |
| { "hour", 4, (60 * 60), false }, | |
| { "days", 4, 1, true }, | |
| { "day", 3, 1, true }, | |
| { "weeks", 5, 7, true }, | |
| { "week", 4, 7, true }, | |
| { NULL, 0, 0, false }, | |
| }; | |
| /* | |
| * Add a number of calendar days (which may be negative) to a time, preserving | |
| * the wall-clock time across Daylight Saving Time transitions (same approach | |
| * as _advance_time()). | |
| * Returns (time_t) -1 on error or if days is out of the int range of tm_mday. | |
| */ | |
| static time_t _add_days(time_t from, long days) | |
| { | |
| struct tm tm; | |
| localtime_r(&from, &tm); | |
| if ((days > ((long) INT_MAX - tm.tm_mday)) || | |
| (days < ((long) INT_MIN + 31))) | |
| return (time_t) -1; | |
| tm.tm_mday += days; | |
| return slurm_mktime(&tm); | |
| } | |
| /* _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 | |
| * time_now (in): reference time the delta is relative to | |
| * sign (in): 1 to add the delta to time_now, -1 to subtract it | |
| * delta (out): signed delta in seconds | |
| * RET: -1 on error, 0 otherwise | |
| */ | |
| static int _get_delta(const char *time_str, int *pos, time_t time_now, int sign, | |
| long *delta) | |
| { | |
| int i, offset; | |
| long cnt = 0; | |
| int digit = 0; | |
| bool calendar = false; | |
| 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; | |
| calendar = un[i].calendar_days; | |
| 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; | |
| if (sign < 0) | |
| cnt = -cnt; | |
| /* | |
| * Days and weeks are calendar units: keep the same wall-clock time | |
| * across DST changes. Smaller units are plain elapsed seconds. | |
| */ | |
| if (calendar) { | |
| time_t later = _add_days(time_now, cnt); | |
| if (later == (time_t) -1) | |
| goto prob; | |
| cnt = later - time_now; | |
| } | |
| *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. | |
| */ | |
| static int _parse_time(const char *time_str, int past, timespec_t *ts_ptr, | |
| int *pos_ptr) | |
| { | |
| 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; | |
| ts_ptr->tv_sec = (time_t) uts; | |
| return SLURM_SUCCESS; | |
| } | |
| 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; | |
| struct tm later_tm; | |
| later = _add_days(time_now, 1); | |
| if (later == (time_t) -1) | |
| goto prob; | |
| 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, time_now, | |
| 1, &delta)) | |
| goto prob; | |
| break; | |
| } | |
| if (time_str[i] == '-') { | |
| pos += i; | |
| if (_get_delta(time_str, &pos, time_now, | |
| -1, &delta)) | |
| goto prob; | |
| 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 SLURM_SUCCESS; | |
| 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; | |
| struct tm later_tm; | |
| later = _add_days(time_now, 1); | |
| if (later == (time_t) -1) | |
| goto prob; | |
| 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) { | |
| ts_ptr->tv_sec = ret_time; | |
| return SLURM_SUCCESS; | |
| } | |
| prob: | |
| *pos_ptr = pos; | |
| return ESLURM_INVALID_TIME_VALUE; | |
| } | |
| extern time_t parse_time(const char *time_str, int past) | |
| { | |
| int rc = EINVAL, pos = -1; | |
| timespec_t ts = { 0, 0 }; | |
| if ((rc = _parse_time(time_str, past, &ts, &pos))) { | |
| errno = rc; | |
| fprintf(stderr, "Invalid time specification (pos=%d): %s\n", | |
| pos, time_str); | |
| } | |
| return ts.tv_sec; | |
| } | |
| extern int parse_timespec(const char *time_str, const bool past, | |
| timespec_t *ts_ptr) | |
| { | |
| int rc = EINVAL, pos = -1; | |
| timespec_t ts = { 0, 0 }; | |
| bool is_integer = true; | |
| if (!time_str || !time_str[0]) | |
| return EINVAL; | |
| /* Handle [0-9]+ as seconds */ | |
| for (pos = 0; time_str[pos]; pos++) { | |
| if ((time_str[pos] < '0') || (time_str[pos] > '9')) { | |
| is_integer = false; | |
| break; | |
| } | |
| } | |
| if (is_integer) { | |
| const unsigned long secs = slurm_atoul(time_str); | |
| if (secs != ULONG_MAX) { | |
| ts_ptr->tv_sec = secs; | |
| return SLURM_SUCCESS; | |
| } | |
| } | |
| if ((rc = _parse_time(time_str, (past ? 1 : 0), &ts, &pos))) { | |
| debug3("%s: Parsing %s @ %d failed: %s", | |
| __func__, time_str, pos, slurm_strerror(rc)); | |
| return rc; | |
| } | |
| *ts_ptr = ts; | |
| return SLURM_SUCCESS; | |
| } | |
| /* | |
| * 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); | |
| } | |
| } | |
| } |