summaryrefslogtreecommitdiff
path: root/src/lib/libcmd/date.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/libcmd/date.c')
-rw-r--r--src/lib/libcmd/date.c515
1 files changed, 515 insertions, 0 deletions
diff --git a/src/lib/libcmd/date.c b/src/lib/libcmd/date.c
new file mode 100644
index 0000000..3f9a303
--- /dev/null
+++ b/src/lib/libcmd/date.c
@@ -0,0 +1,515 @@
+/***********************************************************************
+* *
+* This software is part of the ast package *
+* Copyright (c) 1992-2012 AT&T Intellectual Property *
+* and is licensed under the *
+* Eclipse Public License, Version 1.0 *
+* by AT&T Intellectual Property *
+* *
+* A copy of the License is available at *
+* http://www.eclipse.org/org/documents/epl-v10.html *
+* (with md5 checksum b35adb5213ca9657e911e9befb180842) *
+* *
+* Information and Software Systems Research *
+* AT&T Research *
+* Florham Park NJ *
+* *
+* Glenn Fowler <gsf@research.att.com> *
+* David Korn <dgk@research.att.com> *
+* *
+***********************************************************************/
+#pragma prototyped
+/*
+ * Glenn Fowler
+ * AT&T Research
+ *
+ * date -- set/display date
+ */
+
+static const char usage[] =
+"[-?\n@(#)$Id: date (AT&T Research) 2011-01-27 $\n]"
+USAGE_LICENSE
+"[+NAME?date - set/list/convert dates]"
+"[+DESCRIPTION?\bdate\b sets the current date and time (with appropriate"
+" privilege), lists the current date or file dates, or converts"
+" dates.]"
+"[+?Most common \adate\a forms are recognized, including those for"
+" \bcrontab\b(1), \bls\b(1), \btouch\b(1), and the default"
+" output from \bdate\b itself.]"
+"[+?If the \adate\a operand consists of 4, 6, 8, 10 or 12 digits followed"
+" by an optional \b.\b and two digits then it is interpreted as:"
+" \aHHMM.SS\a, \addHHMM.SS\a, \ammddHHMM.SS\a, \ammddHHMMyy.SS\a or"
+" \ayymmddHHMM.SS\a, or \ammddHHMMccyy.SS\a or \accyymmddHHMM.SS\a."
+" Conflicting standards and practice allow a leading or trailing"
+" 2 or 4 digit year for the 10 and 12 digit forms; the X/Open trailing"
+" form is used to disambiguate (\btouch\b(1) uses the leading form.)"
+" Avoid the 10 digit form to avoid confusion. The digit fields are:]{"
+" [+cc?Century - 1, 19-20.]"
+" [+yy?Year in century, 00-99.]"
+" [+mm?Month, 01-12.]"
+" [+dd?Day of month, 01-31.]"
+" [+HH?Hour, 00-23.]"
+" [+MM?Minute, 00-59.]"
+" [+SS?Seconds, 00-60.]"
+"}"
+"[+?If more than one \adate\a operand is specified then:]{"
+" [+1.?Each operand sets the reference date for the next"
+" operand.]"
+" [+2.?The date is listed for each operand.]"
+" [+3.?The system date is not set.]"
+"}"
+
+"[a:access-time|atime?List file argument access times.]"
+"[c:change-time|ctime?List file argument change times.]"
+"[d:date?Use \adate\a as the current date and do not set the system"
+" clock.]:[date]"
+"[e:epoch?Output the date in seconds since the epoch."
+" Equivalent to \b--format=%s\b.]"
+"[E:elapsed?Interpret pairs of arguments as start and stop dates, sum the"
+" differences between all pairs, and list the result as a"
+" \bfmtelapsed\b(3) elapsed time on the standard output. If there are"
+" an odd number of arguments then the last time argument is differenced"
+" with the current time.]"
+"[f:format?Output the date according to the \bstrftime\b(3) \aformat\a."
+" For backwards compatibility, a first argument of the form"
+" \b+\b\aformat\a is equivalent to \b-f\b format."
+" \aformat\a is in \bprintf\b(3) style, where %\afield\a names"
+" a fixed size field, zero padded if necessary,"
+" and \\\ac\a and \\\annn\a sequences are as in C. Invalid"
+" %\afield\a specifications and all other characters are copied"
+" without change. \afield\a may be preceded by \b%-\b to turn off"
+" padding or \b%_\b to pad with space, otherwise numeric fields"
+" are padded with \b0\b and string fields are padded with space."
+" \afield\a may also be preceded by \bE\b for alternate era"
+" representation or \bO\b for alternate digit representation (if"
+" supported by the current locale.) Finally, an integral \awidth\a"
+" preceding \afield\a truncates the field to \awidth\a characters."
+" The fields are:]:[format]{"
+" [+%?% character]"
+" [+a?abbreviated weekday name]"
+" [+A?full weekday name]"
+" [+b?abbreviated month name]"
+" [+B?full month name]"
+" [+c?\bctime\b(3) style date without the trailing newline]"
+" [+C?2-digit century]"
+" [+d?day of month number]"
+" [+D?date as \amm/dd/yy\a]"
+" [+e?blank padded day of month number]"
+" [+f?locale default override date format]"
+" [+F?%ISO 8601:2000 standard date format; equivalent to Y-%m-%d]"
+" [+g?\bls\b(1) \b-l\b recent date with \ahh:mm\a]"
+" [+G?\bls\b(1) \b-l\b distant date with \ayyyy\a]"
+" [+h?abbreviated month name]"
+" [+H?24-hour clock hour]"
+" [+i?international \bdate\b(1) date with time zone type name]"
+" [+I?12-hour clock hour]"
+" [+j?1-offset Julian date]"
+" [+J?0-offset Julian date]"
+" [+k?\bdate\b(1) style date]"
+" [+K?all numeric date; equivalent to \b%Y-%m-%d+%H:%M:%S\b; \b%_[EO]]K\b for space separator, %OK adds \b.%N\b, \b%EK\b adds \b%.N%z\b, \b%_EK\b adds \b.%N %z\b]"
+" [+l?\bls\b(1) \b-l\b date; equivalent to \b%Q/%g/%G/\b]"
+" [+L?locale default date format]"
+" [+m?month number]"
+" [+M?minutes]"
+" [+n?newline character]"
+" [+N?nanoseconds 000000000-999999999]"
+" [+p?meridian (e.g., \bAM\b or \bPM\b)]"
+" [+q?time zone type name (nation code)]"
+" [+Q?\a<del>recent<del>distant<del>\a: \a<del>\a is a unique"
+" delimter character; \arecent\a format for recent"
+" dates, \adistant\a format otherwise]"
+" [+r?12-hour time as \ahh:mm:ss meridian\a]"
+" [+R?24-hour time as \ahh:mm\a]"
+" [+s?number of seconds since the epoch; \a.prec\a preceding"
+" \bs\b appends \aprec\a nanosecond digits, \b9\b if"
+" \aprec\a is omitted]"
+" [+S?seconds 00-60]"
+" [+t?tab character]"
+" [+T?24-hour time as \ahh:mm:ss\a]"
+" [+u?weekday number 1(Monday)-7]"
+" [+U?week number with Sunday as the first day]"
+" [+V?ISO week number (i18n is \afun\a)]"
+" [+w?weekday number 0(Sunday)-6]"
+" [+W?week number with Monday as the first day]"
+" [+x?locale date style that includes month, day and year]"
+" [+X?locale time style that includes hours and minutes]"
+" [+y?2-digit year (you'll be sorry)]"
+" [+Y?4-digit year]"
+" [+z?time zone \aSHHMM\a west of GMT offset where S is"
+" \b+\b or \b-\b, use pad _ for \aSHH:MM\a]"
+" [+Z?time zone name]"
+" [+=[=]][-+]]flag?set (default or +) or clear (-) \aflag\a"
+" for the remainder of \aformat\a, or for the remainder"
+" of the process if \b==\b is specified. \aflag\a may be:]{"
+" [+l?enable leap second adjustments]"
+" [+n?convert \b%S\b as \b%S.%N\b]"
+" [+u?UTC time zone]"
+" }"
+" [+#?equivalent to %s]"
+" [+??alternate?use \aalternate\a format if a default format"
+" override has not been specified, e.g., \bls\b(1) uses"
+" \"%?%l\"; export TM_OPTIONS=\"format='\aoverride\a'\""
+" to override the default]"
+"}"
+"[i:incremental|adjust?Set the system time in incrementatl adjustments to"
+" avoid complete time shift shock. Negative adjustments still maintain"
+" monotonic increasing time. Not available on all systems.]"
+"[L:last?List only the last time for multiple \adate\a operands.]"
+"[l:leap-seconds?Include leap seconds in time calculations. Leap seconds"
+" after the ast library release date are not accounted for.]"
+"[m:modify-time|mtime?List file argument modify times.]"
+"[n!:network?Set network time.]"
+"[p:parse?Add \aformat\a to the list of \bstrptime\b(3) parse conversion"
+" formats. \aformat\a follows the same conventions as the"
+" \b--format\b option, with the addition of these format"
+" fields:]:[format]{"
+" [+|?If the format failed before this point then restart"
+" the parse with the remaining format.]"
+" [+&?Call the \btmdate\b(3) heuristic parser. This is"
+" is the default when \b--parse\b is omitted.]"
+"}"
+"[R:rfc-2822?List date and time in RFC 2822 format "
+ "(%a, %-e %h %Y %H:%M:%S %z).]"
+"[T:rfc-3339?List date and time in RFC 3339 format according to "
+ "\atype\a:]:[type]"
+ "{"
+ "[d:date?(%Y-%m-%d)]"
+ "[s:seconds?(%Y-%m-%d %H:%M:%S%_z)]"
+ "[n:ns|nanoseconds?(%Y-%m-%d %H:%M:%S.%N%_z)]"
+ "}"
+"[s:show?Show the date without setting the system time.]"
+"[u:utc|gmt|zulu|universal?Output dates in \acoordinated universal time\a (UTC).]"
+"[U:unelapsed?Interpret each argument as \bfmtelapsed\b(3) elapsed"
+" time and list the \bstrelapsed\b(3) 1/\ascale\a seconds.]#[scale]"
+"[z:list-zones?List the known time zone table and exit. The table columns"
+" are: country code, standard zone name, savings time zone name,"
+" minutes west of \bUTC\b, and savings time minutes offset. Blank"
+" or empty entries are listed as \b-\b.]"
+
+"\n"
+"\n[ +format | date ... | file ... ]\n"
+"\n"
+
+"[+SEE ALSO?\bcrontab\b(1), \bls\b(1), \btouch\b(1), \bfmtelapsed\b(3),"
+" \bstrftime\b(3), \bstrptime\b(3), \btm\b(3)]"
+;
+
+#include <cmd.h>
+#include <ls.h>
+#include <proc.h>
+#include <tmx.h>
+#include <times.h>
+
+typedef struct Fmt
+{
+ struct Fmt* next;
+ char* format;
+} Fmt_t;
+
+#ifndef ENOSYS
+#define ENOSYS EINVAL
+#endif
+
+/*
+ * set the system clock
+ * the standards wimped out here
+ */
+
+static int
+settime(Shbltin_t* context, const char* cmd, Time_t now, int adjust, int network)
+{
+ char* s;
+ char** argv;
+ char* args[5];
+ char buf[1024];
+
+ if (!adjust && !network)
+ return tmxsettime(now);
+ argv = args;
+ s = "/usr/bin/date";
+ if (!streq(cmd, s) && (!eaccess(s, X_OK) || !eaccess(s+=4, X_OK)))
+ {
+ *argv++ = s;
+ if (streq(astconf("UNIVERSE", NiL, NiL), "att"))
+ {
+ tmxfmt(buf, sizeof(buf), "%m%d%H" "%M%Y.%S", now);
+ if (adjust)
+ *argv++ = "-a";
+ }
+ else
+ {
+ tmxfmt(buf, sizeof(buf), "%Y%m%d%H" "%M.%S", now);
+ if (network)
+ *argv++ = "-n";
+ if (tm_info.flags & TM_UTC)
+ *argv++ = "-u";
+ }
+ *argv++ = buf;
+ *argv = 0;
+ if (!sh_run(context, argv - args, args))
+ return 0;
+ }
+ return -1;
+}
+
+/*
+ * convert s to Time_t with error checking
+ */
+
+static Time_t
+convert(register Fmt_t* f, char* s, Time_t now)
+{
+ char* t;
+ char* u;
+
+ do
+ {
+ now = tmxscan(s, &t, f->format, &u, now, 0);
+ if (!*t && (!f->format || !*u))
+ break;
+ } while (f = f->next);
+ if (!f || *t)
+ error(3, "%s: invalid date specification", f ? t : s);
+ return now;
+}
+
+int
+b_date(int argc, register char** argv, Shbltin_t* context)
+{
+ register int n;
+ register char* s;
+ register Fmt_t* f;
+ char* t;
+ unsigned long u;
+ Time_t now;
+ Time_t ts;
+ Time_t te;
+ Time_t e;
+ char buf[1024];
+ Fmt_t* fmts;
+ Fmt_t fmt;
+ struct stat st;
+
+ char* cmd = argv[0]; /* original command path */
+ char* format = 0; /* tmxfmt() format */
+ char* string = 0; /* date string */
+ int elapsed = 0; /* args are start/stop pairs */
+ int filetime = 0; /* use this st_ time field */
+ int increment = 0; /* incrementally adjust time */
+ int last = 0; /* display the last time arg */
+ Tm_zone_t* listzones = 0; /* known time zone table */
+ int network = 0; /* don't set network time */
+ int show = 0; /* show date and don't set */
+ int unelapsed = 0; /* fmtelapsed() => strelapsed */
+
+ cmdinit(argc, argv, context, ERROR_CATALOG, 0);
+ tm_info.flags = TM_DATESTYLE;
+ fmts = &fmt;
+ fmt.format = "";
+ fmt.next = 0;
+ for (;;)
+ {
+ switch (optget(argv, usage))
+ {
+ case 'a':
+ case 'c':
+ case 'm':
+ filetime = opt_info.option[1];
+ continue;
+ case 'd':
+ string = opt_info.arg;
+ show = 1;
+ continue;
+ case 'e':
+ format = "%s";
+ continue;
+ case 'E':
+ elapsed = 1;
+ continue;
+ case 'f':
+ format = opt_info.arg;
+ continue;
+ case 'i':
+ increment = 1;
+ continue;
+ case 'l':
+ tm_info.flags |= TM_LEAP;
+ continue;
+ case 'L':
+ last = 1;
+ continue;
+ case 'n':
+ network = 1;
+ continue;
+ case 'p':
+ if (!(f = newof(0, Fmt_t, 1, 0)))
+ error(ERROR_SYSTEM|3, "out of space [format]");
+ f->next = fmts;
+ f->format = opt_info.arg;
+ fmts = f;
+ continue;
+ case 'R':
+ format = "%a, %-e %h %Y %H:%M:%S %z";
+ continue;
+ case 's':
+ show = 1;
+ continue;
+ case 'T':
+ switch (opt_info.num)
+ {
+ case 'd':
+ format = "%Y-%m-%d";
+ continue;
+ case 'n':
+ format = "%Y-%m-%d %H:%M:%S.%N%_z";
+ continue;
+ case 's':
+ format = "%Y-%m-%d %H:%M:%S%_z";
+ continue;
+ }
+ continue;
+ case 'u':
+ tm_info.flags |= TM_UTC;
+ continue;
+ case 'U':
+ unelapsed = (int)opt_info.num;
+ continue;
+ case 'z':
+ listzones = tm_data.zone;
+ continue;
+ case '?':
+ error(ERROR_USAGE|4, "%s", opt_info.arg);
+ continue;
+ case ':':
+ error(2, "%s", opt_info.arg);
+ continue;
+ }
+ break;
+ }
+ argv += opt_info.index;
+ if (error_info.errors)
+ error(ERROR_USAGE|4, "%s", optusage(NiL));
+ now = tmxgettime();
+ if (listzones)
+ {
+ s = "-";
+ while (listzones->standard)
+ {
+ if (listzones->type)
+ s = listzones->type;
+ sfprintf(sfstdout, "%3s %4s %4s %4d %4d\n", s, *listzones->standard ? listzones->standard : "-", listzones->daylight ? listzones->daylight : "-", listzones->west, listzones->dst);
+ listzones++;
+ show = 1;
+ }
+ }
+ else if (elapsed)
+ {
+ e = 0;
+ while (s = *argv++)
+ {
+ if (!(t = *argv++))
+ {
+ argv--;
+ t = "now";
+ }
+ ts = convert(fmts, s, now);
+ te = convert(fmts, t, now);
+ if (te > ts)
+ e += te - ts;
+ else
+ e += ts - te;
+ }
+ sfputr(sfstdout, fmtelapsed((unsigned long)tmxsec(e), 1), '\n');
+ show = 1;
+ }
+ else if (unelapsed)
+ {
+ while (s = *argv++)
+ {
+ u = strelapsed(s, &t, unelapsed);
+ if (*t)
+ error(3, "%s: invalid elapsed time", s);
+ sfprintf(sfstdout, "%lu\n", u);
+ }
+ show = 1;
+ }
+ else if (filetime)
+ {
+ if (!*argv)
+ error(ERROR_USAGE|4, "%s", optusage(NiL));
+ n = argv[1] != 0;
+ while (s = *argv++)
+ {
+ if (stat(s, &st))
+ error(2, "%s: not found", s);
+ else
+ {
+ switch (filetime)
+ {
+ case 'a':
+ now = tmxgetatime(&st);
+ break;
+ case 'c':
+ now = tmxgetctime(&st);
+ break;
+ default:
+ now = tmxgetmtime(&st);
+ break;
+ }
+ tmxfmt(buf, sizeof(buf), format, now);
+ if (n)
+ sfprintf(sfstdout, "%s: %s\n", s, buf);
+ else
+ sfprintf(sfstdout, "%s\n", buf);
+ show = 1;
+ }
+ }
+ }
+ else
+ {
+ if ((s = *argv) && !format && *s == '+')
+ {
+ format = s + 1;
+ argv++;
+ s = *argv;
+ }
+ if (s || (s = string))
+ {
+ if (*argv && string)
+ error(ERROR_USAGE|4, "%s", optusage(NiL));
+ now = convert(fmts, s, now);
+ if (*argv && (s = *++argv))
+ {
+ show = 1;
+ do
+ {
+ if (!last)
+ {
+ tmxfmt(buf, sizeof(buf), format, now);
+ sfprintf(sfstdout, "%s\n", buf);
+ }
+ now = convert(fmts, s, now);
+ } while (s = *++argv);
+ }
+ }
+ else
+ show = 1;
+ if (format || show)
+ {
+ tmxfmt(buf, sizeof(buf), format, now);
+ sfprintf(sfstdout, "%s\n", buf);
+ }
+ else if (settime(context, cmd, now, increment, network))
+ error(ERROR_SYSTEM|3, "cannot set system time");
+ }
+ while (fmts != &fmt)
+ {
+ f = fmts;
+ fmts = fmts->next;
+ free(f);
+ }
+ tm_info.flags = 0;
+ if (show && sfsync(sfstdout))
+ error(ERROR_system(0), "write error");
+ return error_info.errors != 0;
+}