| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include <config.h> |
| | #include <stdio.h> |
| | #include <getopt.h> |
| | #include <sys/types.h> |
| | #if HAVE_LANGINFO_CODESET |
| | # include <langinfo.h> |
| | #endif |
| |
|
| | #include "system.h" |
| | #include "argmatch.h" |
| | #include "parse-datetime.h" |
| | #include "posixtm.h" |
| | #include "quote.h" |
| | #include "show-date.h" |
| | #include "stat-time.h" |
| | #include "xsetenv.h" |
| |
|
| | |
| | #define PROGRAM_NAME "date" |
| |
|
| | #define AUTHORS proper_name ("David MacKenzie") |
| |
|
| | enum Time_spec |
| | { |
| | |
| | TIME_SPEC_DATE, |
| | |
| | TIME_SPEC_SECONDS, |
| | |
| | TIME_SPEC_NS, |
| |
|
| | |
| |
|
| | |
| | TIME_SPEC_HOURS, |
| | |
| | TIME_SPEC_MINUTES |
| | }; |
| |
|
| | static char const *const time_spec_string[] = |
| | { |
| | |
| | |
| | "hours", "minutes", |
| | "date", "seconds", "ns", nullptr |
| | }; |
| | static enum Time_spec const time_spec[] = |
| | { |
| | TIME_SPEC_HOURS, TIME_SPEC_MINUTES, |
| | TIME_SPEC_DATE, TIME_SPEC_SECONDS, TIME_SPEC_NS |
| | }; |
| | ARGMATCH_VERIFY (time_spec_string, time_spec); |
| |
|
| | |
| | static char const rfc_email_format[] = "%a, %d %b %Y %H:%M:%S %z"; |
| |
|
| | |
| | |
| | enum |
| | { |
| | DEBUG_DATE_PARSING_OPTION = CHAR_MAX + 1, |
| | RESOLUTION_OPTION, |
| | RFC_3339_OPTION |
| | }; |
| |
|
| | static char const short_options[] = "d:f:I::r:Rs:u"; |
| |
|
| | static struct option const long_options[] = |
| | { |
| | {"date", required_argument, nullptr, 'd'}, |
| | {"debug", no_argument, nullptr, DEBUG_DATE_PARSING_OPTION}, |
| | {"file", required_argument, nullptr, 'f'}, |
| | {"iso-8601", optional_argument, nullptr, 'I'}, |
| | {"reference", required_argument, nullptr, 'r'}, |
| | {"resolution", no_argument, nullptr, RESOLUTION_OPTION}, |
| | {"rfc-email", no_argument, nullptr, 'R'}, |
| | {"rfc-822", no_argument, nullptr, 'R'}, |
| | {"rfc-2822", no_argument, nullptr, 'R'}, |
| | {"rfc-3339", required_argument, nullptr, RFC_3339_OPTION}, |
| | {"set", required_argument, nullptr, 's'}, |
| | {"uct", no_argument, nullptr, 'u'}, |
| | {"utc", no_argument, nullptr, 'u'}, |
| | {"universal", no_argument, nullptr, 'u'}, |
| | {GETOPT_HELP_OPTION_DECL}, |
| | {GETOPT_VERSION_OPTION_DECL}, |
| | {nullptr, 0, nullptr, 0} |
| | }; |
| |
|
| | |
| | static unsigned int parse_datetime_flags; |
| |
|
| | #if LOCALTIME_CACHE |
| | # define TZSET tzset () |
| | #else |
| | # define TZSET |
| | #endif |
| |
|
| | #ifdef _DATE_FMT |
| | # define DATE_FMT_LANGINFO() nl_langinfo (_DATE_FMT) |
| | #else |
| | # define DATE_FMT_LANGINFO() "" |
| | #endif |
| |
|
| | void |
| | usage (int status) |
| | { |
| | if (status != EXIT_SUCCESS) |
| | emit_try_help (); |
| | else |
| | { |
| | printf (_("\ |
| | Usage: %s [OPTION]... [+FORMAT]\n\ |
| | or: %s [-u|--utc|--universal] [MMDDhhmm[[CC]YY][.ss]]\n\ |
| | "), |
| | program_name, program_name); |
| | fputs (_("\ |
| | Display date and time in the given FORMAT.\n\ |
| | With -s, or with [MMDDhhmm[[CC]YY][.ss]], set the date and time.\n\ |
| | "), stdout); |
| |
|
| | emit_mandatory_arg_note (); |
| |
|
| | fputs (_("\ |
| | -d, --date=STRING display time described by STRING, not 'now'\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | --debug annotate the parsed date, and\n\ |
| | warn about questionable usage to standard error\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | -f, --file=DATEFILE like --date; once for each line of DATEFILE\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | -I[FMT], --iso-8601[=FMT] output date/time in ISO 8601 format.\n\ |
| | FMT='date' for date only (the default),\n\ |
| | 'hours', 'minutes', 'seconds', or 'ns'\n\ |
| | for date and time to the indicated precision.\n\ |
| | Example: 2006-08-14T02:34:56-06:00\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | --resolution output the available resolution of timestamps\n\ |
| | Example: 0.000000001\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | -R, --rfc-email output date and time in RFC 5322 format.\n\ |
| | Example: Mon, 14 Aug 2006 02:34:56 -0600\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | --rfc-3339=FMT output date/time in RFC 3339 format.\n\ |
| | FMT='date', 'seconds', or 'ns'\n\ |
| | for date and time to the indicated precision.\n\ |
| | Example: 2006-08-14 02:34:56-06:00\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | -r, --reference=FILE display the last modification time of FILE\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | -s, --set=STRING set time described by STRING\n\ |
| | -u, --utc, --universal print or set Coordinated Universal Time (UTC)\n\ |
| | "), stdout); |
| | fputs (HELP_OPTION_DESCRIPTION, stdout); |
| | fputs (VERSION_OPTION_DESCRIPTION, stdout); |
| | fputs (_("\ |
| | \n\ |
| | All options that specify the date to display are mutually exclusive.\n\ |
| | I.e.: --date, --file, --reference, --resolution.\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | \n\ |
| | FORMAT controls the output. Interpreted sequences are:\n\ |
| | \n\ |
| | %% a literal %\n\ |
| | %a locale's abbreviated weekday name (e.g., Sun)\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | %A locale's full weekday name (e.g., Sunday)\n\ |
| | %b locale's abbreviated month name (e.g., Jan)\n\ |
| | %B locale's full month name (e.g., January)\n\ |
| | %c locale's date and time (e.g., Thu Mar 3 23:05:25 2005)\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | %C century; like %Y, except omit last two digits (e.g., 20)\n\ |
| | %d day of month (e.g., 01)\n\ |
| | %D date (ambiguous); same as %m/%d/%y\n\ |
| | %e day of month, space padded; same as %_d\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | %F full date; like %+4Y-%m-%d\n\ |
| | %g last two digits of year of ISO week number (ambiguous; 00-99); see %G\n\ |
| | %G year of ISO week number; normally useful only with %V\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | %h same as %b\n\ |
| | %H hour (00..23)\n\ |
| | %I hour (01..12)\n\ |
| | %j day of year (001..366)\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | %k hour, space padded ( 0..23); same as %_H\n\ |
| | %l hour, space padded ( 1..12); same as %_I\n\ |
| | %m month (01..12)\n\ |
| | %M minute (00..59)\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | %n a newline\n\ |
| | %N nanoseconds (000000000..999999999)\n\ |
| | %p locale's equivalent of either AM or PM; blank if not known\n\ |
| | %P like %p, but lower case\n\ |
| | %q quarter of year (1..4)\n\ |
| | %r locale's 12-hour clock time (e.g., 11:11:04 PM)\n\ |
| | %R 24-hour hour and minute; same as %H:%M\n\ |
| | %s seconds since the Epoch (1970-01-01 00:00 UTC)\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | %S second (00..60)\n\ |
| | %t a tab\n\ |
| | %T time; same as %H:%M:%S\n\ |
| | %u day of week (1..7); 1 is Monday\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | %U week number of year, with Sunday as first day of week (00..53)\n\ |
| | %V ISO week number, with Monday as first day of week (01..53)\n\ |
| | %w day of week (0..6); 0 is Sunday\n\ |
| | %W week number of year, with Monday as first day of week (00..53)\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | %x locale's date (can be ambiguous; e.g., 12/31/99)\n\ |
| | %X locale's time representation (e.g., 23:13:48)\n\ |
| | %y last two digits of year (ambiguous; 00..99)\n\ |
| | %Y year\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | %z +hhmm numeric time zone (e.g., -0400)\n\ |
| | %:z +hh:mm numeric time zone (e.g., -04:00)\n\ |
| | %::z +hh:mm:ss numeric time zone (e.g., -04:00:00)\n\ |
| | %:::z numeric time zone with : to necessary precision (e.g., -04, +05:30)\n\ |
| | %Z alphabetic time zone abbreviation (e.g., EDT)\n\ |
| | \n\ |
| | By default, date pads numeric fields with zeroes.\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | The following optional flags may follow '%':\n\ |
| | \n\ |
| | - (hyphen) do not pad the field\n\ |
| | _ (underscore) pad with spaces\n\ |
| | 0 (zero) pad with zeros\n\ |
| | + pad with zeros, and put '+' before future years with >4 digits\n\ |
| | ^ use upper case if possible\n\ |
| | # use opposite case if possible\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | \n\ |
| | After any flags comes an optional field width, as a decimal number;\n\ |
| | then an optional modifier, which is either\n\ |
| | E to use the locale's alternate representations if available, or\n\ |
| | O to use the locale's alternate numeric symbols if available.\n\ |
| | "), stdout); |
| | fputs (_("\ |
| | \n\ |
| | Examples:\n\ |
| | Convert seconds since the Epoch (1970-01-01 UTC) to a date\n\ |
| | $ date --date='@2147483647'\n\ |
| | \n\ |
| | Show the time on the west coast of the US (use tzselect(1) to find TZ)\n\ |
| | $ TZ='America/Los_Angeles' date\n\ |
| | \n\ |
| | Show the local time for 9AM next Friday on the west coast of the US\n\ |
| | $ date --date='TZ=\"America/Los_Angeles\" 09:00 next Fri'\n\ |
| | "), stdout); |
| | emit_ancillary_info (PROGRAM_NAME); |
| | } |
| | exit (status); |
| | } |
| |
|
| | |
| | |
| |
|
| | ATTRIBUTE_CONST |
| | static int |
| | res_width (long int res) |
| | { |
| | int i = 9; |
| | for (long long int r = 1; (r *= 10) <= res; ) |
| | i--; |
| | return i; |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | static char * |
| | adjust_resolution (char const *format) |
| | { |
| | char *copy = nullptr; |
| |
|
| | for (char const *f = format; *f; f++) |
| | if (f[0] == '%') |
| | { |
| | if (f[1] == '-' && f[2] == 'N') |
| | { |
| | if (!copy) |
| | copy = xstrdup (format); |
| | copy[f + 1 - format] = '0' + res_width (gettime_res ()); |
| | f += 2; |
| | } |
| | else |
| | f += f[1] == '%'; |
| | } |
| |
|
| | return copy; |
| | } |
| |
|
| | |
| | |
| | |
| |
|
| | static char * |
| | set_LC_TIME (char const *locale) |
| | { |
| | |
| | |
| | |
| | |
| | |
| | |
| | char const *all = getenv ("LC_ALL"); |
| | if (all != nullptr && *all != '\0') |
| | { |
| | |
| | |
| | |
| | xsetenv ("LC_CTYPE", all, 1); |
| | xsetenv ("LC_TIME", all, 1); |
| | xsetenv ("LC_MESSAGES", all, 1); |
| | xsetenv ("LC_NUMERIC", all, 1); |
| | |
| | |
| | unsetenv ("LC_ALL"); |
| | } |
| |
|
| | |
| | char const *value = getenv ("LC_TIME"); |
| | char *ret = (value == nullptr || *value == '\0' ? nullptr : xstrdup (value)); |
| | if (locale != nullptr) |
| | xsetenv ("LC_TIME", locale, 1); |
| | else |
| | unsetenv ("LC_TIME"); |
| |
|
| | |
| | setlocale (LC_TIME, ""); |
| |
|
| | return ret; |
| | } |
| |
|
| | static bool |
| | show_date_helper (char const *format, bool use_c_locale, |
| | struct timespec when, timezone_t tz) |
| | { |
| | if (parse_datetime_flags & PARSE_DATETIME_DEBUG) |
| | error (0, 0, _("output format: %s"), quote (format)); |
| |
|
| | bool ok; |
| | if (use_c_locale) |
| | { |
| | char *old_locale_category = set_LC_TIME ("C"); |
| | ok = show_date (format, when, tz); |
| | char *new_locale_category = set_LC_TIME (old_locale_category); |
| | free (new_locale_category); |
| | free (old_locale_category); |
| | } |
| | else |
| | ok = show_date (format, when, tz); |
| |
|
| | putchar ('\n'); |
| | return ok; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | static bool |
| | batch_convert (char const *input_filename, |
| | char const *format, bool format_in_c_locale, |
| | timezone_t tz, char const *tzstring) |
| | { |
| | bool ok; |
| | FILE *in_stream; |
| | char *line; |
| | size_t buflen; |
| | struct timespec when; |
| |
|
| | if (streq (input_filename, "-")) |
| | { |
| | input_filename = _("standard input"); |
| | in_stream = stdin; |
| | } |
| | else |
| | { |
| | in_stream = fopen (input_filename, "r"); |
| | if (in_stream == nullptr) |
| | error (EXIT_FAILURE, errno, "%s", quotef (input_filename)); |
| | } |
| |
|
| | line = nullptr; |
| | buflen = 0; |
| | ok = true; |
| | while (true) |
| | { |
| | ssize_t line_length = getline (&line, &buflen, in_stream); |
| | if (line_length < 0) |
| | { |
| | if (ferror (in_stream)) |
| | error (EXIT_FAILURE, errno, _("%s: read error"), |
| | quotef (input_filename)); |
| | break; |
| | } |
| |
|
| | if (! parse_datetime2 (&when, line, nullptr, |
| | parse_datetime_flags, tz, tzstring)) |
| | { |
| | if (line[line_length - 1] == '\n') |
| | line[line_length - 1] = '\0'; |
| | error (0, 0, _("invalid date %s"), quote (line)); |
| | ok = false; |
| | } |
| | else |
| | { |
| | ok &= show_date_helper (format, format_in_c_locale, when, tz); |
| | } |
| | } |
| |
|
| | if (fclose (in_stream) == EOF) |
| | error (EXIT_FAILURE, errno, "%s", quotef (input_filename)); |
| |
|
| | free (line); |
| |
|
| | return ok; |
| | } |
| |
|
| | int |
| | main (int argc, char **argv) |
| | { |
| | int optc; |
| | char const *datestr = nullptr; |
| | char const *set_datestr = nullptr; |
| | struct timespec when; |
| | bool set_date = false; |
| | char const *format = nullptr; |
| | bool format_in_c_locale = false; |
| | bool get_resolution = false; |
| | char *batch_file = nullptr; |
| | char *reference = nullptr; |
| | struct stat refstats; |
| | bool ok; |
| | bool discarded_datestr = false; |
| | bool discarded_set_datestr = false; |
| |
|
| | initialize_main (&argc, &argv); |
| | set_program_name (argv[0]); |
| | setlocale (LC_ALL, ""); |
| | bindtextdomain (PACKAGE, LOCALEDIR); |
| | textdomain (PACKAGE); |
| |
|
| | atexit (close_stdout); |
| |
|
| | while ((optc = getopt_long (argc, argv, short_options, long_options, nullptr)) |
| | != -1) |
| | { |
| | switch (optc) |
| | { |
| | case 'd': |
| | if (datestr) |
| | discarded_datestr = true; |
| | datestr = optarg; |
| | break; |
| | case DEBUG_DATE_PARSING_OPTION: |
| | parse_datetime_flags |= PARSE_DATETIME_DEBUG; |
| | break; |
| | case 'f': |
| | batch_file = optarg; |
| | break; |
| | case RESOLUTION_OPTION: |
| | get_resolution = true; |
| | break; |
| | case RFC_3339_OPTION: |
| | { |
| | static char const rfc_3339_format[][32] = |
| | { |
| | "%Y-%m-%d", |
| | "%Y-%m-%d %H:%M:%S%:z", |
| | "%Y-%m-%d %H:%M:%S.%N%:z" |
| | }; |
| | enum Time_spec i = |
| | XARGMATCH ("--rfc-3339", optarg, |
| | time_spec_string + 2, time_spec + 2); |
| | format = rfc_3339_format[i]; |
| | format_in_c_locale = true; |
| | break; |
| | } |
| | case 'I': |
| | { |
| | static char const iso_8601_format[][32] = |
| | { |
| | "%Y-%m-%d", |
| | "%Y-%m-%dT%H:%M:%S%:z", |
| | "%Y-%m-%dT%H:%M:%S,%N%:z", |
| | "%Y-%m-%dT%H%:z", |
| | "%Y-%m-%dT%H:%M%:z" |
| | }; |
| | enum Time_spec i = |
| | (optarg |
| | ? XARGMATCH ("--iso-8601", optarg, time_spec_string, time_spec) |
| | : TIME_SPEC_DATE); |
| | format = iso_8601_format[i]; |
| | format_in_c_locale = true; |
| | break; |
| | } |
| | case 'r': |
| | reference = optarg; |
| | break; |
| | case 'R': |
| | format = rfc_email_format; |
| | format_in_c_locale = true; |
| | break; |
| | case 's': |
| | if (set_datestr) |
| | discarded_set_datestr = true; |
| | set_datestr = optarg; |
| | set_date = true; |
| | break; |
| | case 'u': |
| | |
| | |
| | |
| | if (putenv (bad_cast ("TZ=UTC0")) != 0) |
| | xalloc_die (); |
| | TZSET; |
| | break; |
| | case_GETOPT_HELP_CHAR; |
| | case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS); |
| | default: |
| | usage (EXIT_FAILURE); |
| | } |
| | } |
| |
|
| | int option_specified_date = (!!datestr + !!batch_file + !!reference |
| | + get_resolution); |
| |
|
| | if (option_specified_date > 1) |
| | { |
| | error (0, 0, |
| | _("the options to specify dates for printing are mutually exclusive")); |
| | usage (EXIT_FAILURE); |
| | } |
| |
|
| | if (set_date && option_specified_date) |
| | { |
| | error (0, 0, |
| | _("the options to print and set the time may not be used together")); |
| | usage (EXIT_FAILURE); |
| | } |
| |
|
| | if (discarded_datestr && (parse_datetime_flags & PARSE_DATETIME_DEBUG)) |
| | error (0, 0, _("only using last of multiple -d options")); |
| |
|
| | if (discarded_set_datestr && (parse_datetime_flags & PARSE_DATETIME_DEBUG)) |
| | error (0, 0, _("only using last of multiple -s options")); |
| |
|
| | if (optind < argc) |
| | { |
| | if (optind + 1 < argc) |
| | { |
| | error (0, 0, _("extra operand %s"), quote (argv[optind + 1])); |
| | usage (EXIT_FAILURE); |
| | } |
| |
|
| | if (argv[optind][0] == '+') |
| | { |
| | if (format) |
| | error (EXIT_FAILURE, 0, _("multiple output formats specified")); |
| | format = argv[optind++] + 1; |
| | } |
| | else if (set_date || option_specified_date) |
| | { |
| | error (0, 0, |
| | _("the argument %s lacks a leading '+';\n" |
| | "when using an option to specify date(s), any non-option\n" |
| | "argument must be a format string beginning with '+'"), |
| | quote (argv[optind])); |
| | usage (EXIT_FAILURE); |
| | } |
| | } |
| |
|
| | if (!format) |
| | { |
| | if (get_resolution) |
| | format = "%s.%N"; |
| | else |
| | { |
| | format = DATE_FMT_LANGINFO (); |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | if (! *format) |
| | format = "%a %b %e %H:%M:%S %Z %Y"; |
| | } |
| | } |
| |
|
| | char *format_copy = adjust_resolution (format); |
| | char const *format_res = format_copy ? format_copy : format; |
| | char const *tzstring = getenv ("TZ"); |
| | timezone_t tz = tzalloc (tzstring); |
| |
|
| | if (batch_file != nullptr) |
| | ok = batch_convert (batch_file, format_res, format_in_c_locale, |
| | tz, tzstring); |
| | else |
| | { |
| | bool valid_date = true; |
| | ok = true; |
| |
|
| | if (!option_specified_date && !set_date) |
| | { |
| | if (optind < argc) |
| | { |
| | |
| | |
| | set_date = true; |
| | datestr = argv[optind]; |
| | valid_date = posixtime (&when.tv_sec, |
| | datestr, |
| | (PDS_TRAILING_YEAR |
| | | PDS_CENTURY | PDS_SECONDS)); |
| | when.tv_nsec = 0; |
| | } |
| | else |
| | { |
| | |
| | gettime (&when); |
| | } |
| | } |
| | else |
| | { |
| | |
| | if (reference != nullptr) |
| | { |
| | if (stat (reference, &refstats) != 0) |
| | error (EXIT_FAILURE, errno, "%s", quotef (reference)); |
| | when = get_stat_mtime (&refstats); |
| | } |
| | else if (get_resolution) |
| | { |
| | long int res = gettime_res (); |
| | when.tv_sec = res / TIMESPEC_HZ; |
| | when.tv_nsec = res % TIMESPEC_HZ; |
| | } |
| | else |
| | { |
| | if (set_datestr) |
| | datestr = set_datestr; |
| | valid_date = parse_datetime2 (&when, datestr, nullptr, |
| | parse_datetime_flags, |
| | tz, tzstring); |
| | } |
| | } |
| |
|
| | if (! valid_date) |
| | error (EXIT_FAILURE, 0, _("invalid date %s"), quote (datestr)); |
| |
|
| | if (set_date) |
| | { |
| | |
| | |
| | if (settime (&when) != 0) |
| | { |
| | error (0, errno, _("cannot set date")); |
| | ok = false; |
| | } |
| | } |
| |
|
| | ok &= show_date_helper (format_res, format_in_c_locale, when, tz); |
| | } |
| |
|
| | main_exit (ok ? EXIT_SUCCESS : EXIT_FAILURE); |
| | } |
| |
|