| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #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); |
| } |
|
|