/* strptime - take a date-time string and turn it into seconds since the epoch. */ /* See Makefile for compilation details. */ /* Copyright (C) 2023 Free Software Foundation, Inc. This file is part of GNU Bash. Bash 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 3 of the License, or (at your option) any later version. Bash 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 Bash. If not, see . */ #include #if defined (HAVE_UNISTD_H) # include #endif #include "bashtypes.h" #include "posixtime.h" #include #include "builtins.h" #include "shell.h" #include "bashgetopt.h" #include "common.h" struct date_modifier { char *shorthand; int incr; }; static struct date_modifier date_time_modifiers[] = { { "now", 0 }, { "today", 0 }, { "tomorrow", 24*60*60 }, { "yesterday", -24*60*60 }, { "day after tomorrow", 48*60*60 }, { "two days ago", -48*60*60 }, { "next week", 7*24*60*60 }, { "last week", -7*24*60*60 }, { "the day after tomorrow", 48*60*60 }, { 0, 0 } }; static char * const date_time_formats[] = { "%a %b %d %T %Z %Y", /* Unix date */ "%a %b %d %T %Y", /* Wkd Mon DD HH:MM:SS YYYY */ "%FT%T%z", /* ISO8601 time YYYY-mm-ddTHH:MM:SSzone */ "%FT%R%z", /* ISO8601 time YYYY-mm-ddTHH:MMzone */ "%G-%m-%dT%T%z", /* ISO8601 time YYYY-mm-ddTHH:MM:SSzone */ "%G-%m-%dT%R%z", /* ISO8601 time YYYY-mm-ddTHH:MMzone */ "%G-%m-%d", /* ISO8601 time YYYY-mm-dd */ /* Can't do 8601 time zone offset with colon or fractions of a second */ "%a, %d %b %Y %T %Z", /* RFC822/RFC2822 time */ "%a, %d %b %Y %T %z", /* RFC822/RFC2822 time */ "%D %T", /* mm/dd/yy HH:MM:SS */ "%D %R", /* mm/dd/yy HH:MM */ "%D %r", /* mm/dd/yy HH:MM:SS a.m. */ "%D %I:%M %p", /* mm/dd/yy HH:MM p.m. */ "%m/%d/%Y %T", /* mm/dd/YYYY HH:MM:SS */ "%m/%d/%Y %R", /* mm/dd/YYYY HH:MM */ "%m/%d/%Y %r", /* mm/dd/YYYY HH:MM:SS a.m */ "%m/%d/%Y %I:%M %p", /* mm/dd/YYYY HH:MM p.m. */ "%m-%d-%Y %T", /* mm-dd-YYYY HH:MM:SS */ "%m-%d-%Y %R", /* mm-dd-YYYY HH:MM */ "%m-%d-%Y %r", /* mm-dd-YYYY HH:MM:SS a.m. */ "%m-%d-%Y %I:%M %p", /* mm-dd-YYYY HH:MM p.m. */ "%Y/%m/%d %T", /* YYYY/mm/dd HH:MM:SS */ "%Y/%m/%d %R", /* YYYY/mm/dd HH:MM */ "%Y/%m/%d %r", /* YYYY/mm/dd hh:MM:SS a.m. */ "%F %T", /* YYYY-mm-dd HH:MM:SS */ "%F %r", /* YYYY-mm-dd HH:MM:SS p.m. */ "%F %R", /* YYYY-mm-dd HH:MM */ "%F %I:%M %p", /* YYYY-mm-dd HH:MM a.m. */ "%F", /* YYYY-mm-dd ISO8601 time */ "%T", /* HH:MM:SS */ "%H.%M.%S", /* HH.MM.SS */ /* From coreutils-9.2 date */ "%Y-%m-%dT%H:%M:%S%z", /* ISO8601 time */ "%Y-%m-%dT%H%z", /* ISO8601 time */ "%Y-%m-%dT%H:%M%z", /* ISO8601 time */ /* RFC 3339 time */ "%Y-%m-%d %H:%M:%S%z", /* RFC 3339 time */ "%Y-%m-%dT%H:%M:%S%z", /* RFC 3339 time */ /* more oddball formats */ "%m.%d.%Y %T", /* mm.dd.YYYY HH:MM:SS */ "%m.%d.%Y %R", /* mm.dd.YYYY HH:MM */ "%m.%d.%Y %r", /* mm.dd.YYYY HH:MM:SS a.m. */ "%m.%d.%Y %I:%M %p", /* mm.dd.YYYY HH:MM p.m. */ "%m/%d/%Y", /* mm/dd/YYYY */ "%d %B %Y %T", /* dd Month YYYY HH:MM:SS */ "%d %B %Y %R", /* dd Month YYYY HH:MM */ "%d %B %Y %r", /* dd Month YYYY HH:MM:SS a.m. */ "%d %B %Y %I:%M %p", /* dd Month YYYY HH:MM p.m. */ "%d %b %Y %T", /* dd Mon YYYY HH:MM:SS */ "%d %b %Y %R", /* dd Mon YYYY HH:MM */ "%d %b %Y %r", /* dd Mon YYYY HH:MM:SS a.m. */ "%d %b %Y %I:%M %p", /* dd Mon YYYY HH:MM p.m. */ "%b %d, %Y %T", /* Mon dd, YYYY HH:MM:SS */ "%b %d, %Y %R", /* Mon dd, YYYY HH:MM */ "%b %d, %Y %r", /* Mon dd, YYYY HH:MM:SS a.m. */ "%b %d, %Y %I:%M %p", /* Mon dd, YYYY HH:MM p.m. */ "%m-%b-%Y", /* dd-Mon-YYYY */ "%m-%b-%Y %T", /* dd-Mon-YYYY HH:MM:SS */ "%m-%b-%Y %R", /* dd-Mon-YYYY HH:MM */ "%m-%b-%Y %r", /* dd-Mon-YYYY HH:MM:SS a.m. */ "%m-%b-%Y %I:%M %p", /* dd-Mon-YYYY HH:MM p.m. */ "%d/%b/%Y:%T %z", /* NCSA log format dd/Mon/YYYY:HH:MM:SS zone */ "%d/%b/%Y:%T%z", /* NCSA log format dd/Mon/YYYY:HH:MM:SSzone */ /* No delimiters */ "%Y%m%d %T", /* YYYYMMDD HH:MM:SS */ "%Y%m%d %R", /* YYYYMMDD HH:MM */ "%Y%m%d %r", /* YYYYMMDD HH:MM:SS a.m. */ "%Y%m%d %I:%M %p", /* YYYYMMDD HH:MM p.m. */ "%Y%m%d %H:%M:%S%z", /* YYYYMMDD HH:MM:SSzone */ "%Y%m%dT%H:%M:%S%z", /* YYYYMMDDTHH:MM:SSzone */ "%Y%m%dT%T", /* YYYYMMDDTHH:MM:SS */ "%Y%m%dT%R", /* YYYYMMDDTHH:MM */ /* Non-US formats */ "%d-%m-%Y", /* dd-mm-YYYY */ "%d-%m-%Y %T", /* dd-mm-YYYY HH:MM:SS */ "%d-%m-%Y %R", /* dd-mm-YYYY HH:MM */ "%d-%m-%Y %r", /* dd-mm-YYYY HH:MM:SS a.m. */ "%d-%m-%Y %I:%M %p", /* dd-mm-YYYY HH:MM p.m. */ "%d/%m/%Y %T", /* dd/mm/YYYY HH:MM:SS */ "%d/%m/%Y %R", /* dd/mm/YYYY HH:MM */ "%d/%m/%Y %r", /* dd/mm/YYYY HH:MM:SS a.m. */ "%d/%m/%Y %I:%M %p", /* dd/mm/YYYY HH:MM p.m. */ "%Y-%d-%m %T", /* YYYY-dd-mm HH:MM:SS */ "%Y-%d-%m %R", /* YYYY-dd-mm HH:MM */ "%d-%m-%Y %T", /* dd-mm-YYYY HH:MM:SS */ "%d-%m-%Y %R", /* dd-mm-YYYY HH:MM */ "%d-%m-%Y %r", /* dd-mm-YYYY HH:MM:SS a.m. */ "%d-%m-%Y %I:%M %p", /* dd-mm-YYYY HH:MM p.m. */ "%d.%m.%Y %T", /* dd.mm.YYYY HH:MM:SS */ "%d.%m.%Y %R", /* dd.mm.YYYY HH:MM */ "%d.%m.%Y %r", /* dd.mm.YYYY HH:MM:SS a.m. */ "%d.%m.%Y %I:%M %p", /* dd.mm.YYYY HH:MM p.m. */ 0 }; static void inittime (time_t *clock, struct tm *timeptr) { timeptr = localtime (clock); /* for now */ /* but default to midnight */ timeptr->tm_hour = timeptr->tm_min = timeptr->tm_sec = 0; /* and let the system figure out the right DST offset */ timeptr->tm_isdst = -1; } int strptime_builtin (WORD_LIST *list) { char *s; struct tm t, *tm; time_t now, secs; char *datestr; int i; if (no_options (list)) /* for now */ return (EX_USAGE); list = loptend; if (list == 0) { builtin_usage (); return (EX_USAGE); } datestr = string_list (list); if (datestr == 0 || *datestr == 0) return (EXECUTION_SUCCESS); now = getnow (); secs = -1; for (i = 0; date_time_modifiers[i].shorthand; i++) { if (STREQ (datestr, date_time_modifiers[i].shorthand)) { secs = now + date_time_modifiers[i].incr; break; } } if (secs == -1) { /* init struct tm */ inittime (&now, tm); t = *tm; for (i = 0; date_time_formats[i]; i++) { s = strptime (datestr, date_time_formats[i], &t); if (s == 0) continue; /* skip extra characters at the end for now */ secs = mktime (&t); break; } } printf ("%ld\n", secs); return (EXECUTION_SUCCESS); } char *strptime_doc[] = { "Convert a date-time string to seconds since the epoch.", "", "Take DATE-TIME, a date-time string, parse it against a set of common", "date-time formats. If the string matches one of the formats, convert", "it into seconds since the epoch and display the result.", (char *)NULL }; /* The standard structure describing a builtin command. bash keeps an array of these structures. The flags must include BUILTIN_ENABLED so the builtin can be used. */ struct builtin strptime_struct = { "strptime", /* builtin name */ strptime_builtin, /* function implementing the builtin */ BUILTIN_ENABLED, /* initial flags for builtin */ strptime_doc, /* array of long documentation strings. */ "strptime date-time", /* usage synopsis; becomes short_doc */ 0 /* reserved for internal use */ };