/*++ Copyright (c) 2013 Minoca Corp. All Rights Reserved Module Name: date.c Abstract: This module implements the date utility, which prints or sets the current system time. Author: Evan Green 8-Oct-2013 Environment: POSIX --*/ // // ------------------------------------------------------------------- Includes // #include #include #include #include #include #include #include #include #include "swlib.h" // // ---------------------------------------------------------------- Definitions // #define DATE_VERSION_MAJOR 1 #define DATE_VERSION_MINOR 0 #define DATE_USAGE \ "usage: date [-u] [+format]\n" \ " date [-u] mmddHHMM[[cc]yy]\n" \ "The date utility prints the current date and time with no arguments.\n" \ "Valid options are:\n" \ " -u -- Perform time operations in UTC (GMT time).\n" \ " +format -- Print the date according to the specified format. The \n" \ " format is the same as that given to the strftime function:\n" \ " %a - Abbreviated weekday.\n" \ " %A - Full weekday.\n" \ " %b - Abbreviated month.\n" \ " %B - Full month.\n" \ " %c - Locale's appropriate date and time representation.\n" \ " %C - Century (year divided by 100 and truncated to an integer).\n"\ " %d - Day of the month [01,31].\n" \ " %D - Date in the format mm/dd/yy.\n" \ " %e - Day of the month in a two digit field with a leading space " \ "fill [1,31].\n" \ " %h - Same as %b.\n" \ " %H - Hour (24-hour clock) [00,23].\n" \ " %I - Hour (12-hour clock) [01,12].\n" \ " %j - Day of the year [001,366].\n" \ " %m - Month [01,12].\n" \ " %M - Minute [00,59].\n" \ " %n - A newline.\n" \ " %N - Nanoseconds [000000000,999999999].\n" \ " %p - Locale's equivalent of AM or PM.\n" \ " %r - 12-hour clock time [01,12] using the AM/PM notation. \n" \ " In POSIX, this is \"%I:%M:%S %p\".\n" \ " %s - Seconds since 1970 GMT.\n" \ " %S - Seconds [00,60].\n" \ " %t - A tab.\n" \ " %T - 24-hour time in the format \"HH:MM:SS\".\n" \ " %u - Weekday as a number [1,7] (1=Monday).\n" \ " %U - Week of the year (Sunday as the first day of the week) \n" \ " [0,53]. All days in a year preceding the first sunday \n" \ " are in week 0.\n" \ " %V - Week of the year (Monday as the first day of the week) \n" \ " [01,53]. If the week containing January 1 has four or \n" \ " more days, it is week 1. Otherwise it is the last week \n" \ " of the previous year.\n" \ " %w - Weekday as a decimal [0,6] (0=Sunday).\n" \ " %W - Week of the year (Monday as the first day of the week) \n" \ " [00,53]. All days preceding the first Monday are week 0.\n" \ " %x - Locale's appropriate date representation.\n" \ " %X - Locale's appropriate time representation.\n" \ " %y - Year within century [00,99].\n" \ " %Y - Year with century.\n" \ " %Z - Timezone name or nothing if no timezone is available.\n" \ " %% - A percent sign character.\n\n" #define DATE_OPTIONS_STRING "u" #define DATE_OPTION_UTC 0x00000001 // // Define a default argument if none is provided. // #define DATE_DEFAULT_ARGUMENT "+%a %b %d %H:%M:%S %Z %Y" // // Define the output format for set time. // #define DATE_SET_TIME_FORMAT "%a %b %d %H:%M:%S %Z %Y" // // Define the size of the buffer used for the strftime function. // #define DATE_TIME_FORMAT_SIZE 2048 // // This value defines the two digit year beginning in which the 21st century is // assumed. // #define TWO_DIGIT_YEAR_CUTOFF 70 // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // INT DateParseComponent ( PSTR *String ); // // -------------------------------------------------------------------- Globals // struct option DateLongOptions[] = { {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {NULL, 0, 0, 0}, }; // // ------------------------------------------------------------------ Functions // INT DateMain ( INT ArgumentCount, CHAR **Arguments ) /*++ Routine Description: This routine is the main entry point for the date utility. Arguments: ArgumentCount - Supplies the number of command line arguments the program was invoked with. Arguments - Supplies a tokenized array of command line arguments. Return Value: Returns an integer exit code. 0 for success, nonzero otherwise. --*/ { PSTR Argument; ULONG ArgumentIndex; INT Option; ULONG Options; struct tm SetTime; int Status; time_t Time; PCHAR TimeBuffer; struct tm *TimeComponents; struct timeval TimeValue; int Year; Options = 0; TimeBuffer = NULL; // // Process the control arguments. // while (TRUE) { Option = getopt_long(ArgumentCount, Arguments, DATE_OPTIONS_STRING, DateLongOptions, NULL); if (Option == -1) { break; } if ((Option == '?') || (Option == ':')) { Status = 1; goto MainEnd; } switch (Option) { case 'u': Options |= DATE_OPTION_UTC; break; case 'V': SwPrintVersion(DATE_VERSION_MAJOR, DATE_VERSION_MINOR); return 1; case 'h': puts(DATE_USAGE); return 1; default: assert(FALSE); Status = 1; goto MainEnd; } } ArgumentIndex = optind; if (ArgumentIndex > ArgumentCount) { ArgumentIndex = ArgumentCount; } Argument = DATE_DEFAULT_ARGUMENT; if (ArgumentIndex < ArgumentCount) { Argument = Arguments[ArgumentIndex]; } // // Fail if there are too many arguments. // if (ArgumentIndex + 1 < ArgumentCount) { SwPrintError(0, Arguments[ArgumentIndex + 1], "Too many arguments"); return 1; } Time = time(NULL); TimeBuffer = malloc(DATE_TIME_FORMAT_SIZE); if (TimeBuffer == NULL) { Status = ENOMEM; goto MainEnd; } // // If the first character of the argument is a +, then print the current // date in the specified format. // if (*Argument == '+') { if ((Options & DATE_OPTION_UTC) != 0) { TimeComponents = gmtime(&Time); } else { TimeComponents = localtime(&Time); } if (TimeComponents == NULL) { Status = errno; SwPrintError(Status, NULL, "Failed to get current time"); goto MainEnd; } strftime(TimeBuffer, DATE_TIME_FORMAT_SIZE, Argument + 1, TimeComponents); printf("%s\n", TimeBuffer); Status = 0; goto MainEnd; } memset(&SetTime, 0, sizeof(struct tm)); // // Parse the string of the form mmddhhmm[[cc]yy]. // Status = EINVAL; SetTime.tm_mon = DateParseComponent(&Argument); if (SetTime.tm_mon < 0) { SwPrintError(0, Argument, "Failed to parse month"); goto MainEnd; } SetTime.tm_mon -= 1; if ((SetTime.tm_mon < 0) || (SetTime.tm_mon > 11)) { SwPrintError(0, Arguments[ArgumentIndex], "Invalid date"); goto MainEnd; } SetTime.tm_mday = DateParseComponent(&Argument); if (SetTime.tm_mday < 0) { SwPrintError(0, Argument, "Failed to parse day"); goto MainEnd; } if ((SetTime.tm_mday < 1) || (SetTime.tm_mday > 31)) { SwPrintError(0, Arguments[ArgumentIndex], "Invalid date"); goto MainEnd; } SetTime.tm_hour = DateParseComponent(&Argument); if (SetTime.tm_hour < 0) { SwPrintError(0, Argument, "Failed to parse hour"); goto MainEnd; } if ((SetTime.tm_hour < 0) || (SetTime.tm_hour > 23)) { SwPrintError(0, Arguments[ArgumentIndex], "Invalid date"); goto MainEnd; } SetTime.tm_min = DateParseComponent(&Argument); if (SetTime.tm_min < 0) { SwPrintError(0, Argument, "Failed to parse minute"); goto MainEnd; } if ((SetTime.tm_hour < 0) || (SetTime.tm_hour > 59)) { SwPrintError(0, Arguments[ArgumentIndex], "Invalid date"); goto MainEnd; } if (*Argument != '\0') { SetTime.tm_year = DateParseComponent(&Argument); if (SetTime.tm_year < 0) { SwPrintError(0, Argument, "Failed to parse year/century"); goto MainEnd; } if (*Argument != '\0') { Year = DateParseComponent(&Argument); if (Year < 0) { SwPrintError(0, Argument, "Failed to parse year"); goto MainEnd; } SetTime.tm_year *= 100; SetTime.tm_year += Year; SetTime.tm_year -= 1900; // // If no century is supplied, then assume the user meant somewhere in // the late 1900's or early 2000's with the cutoff at 1970. // } else { if (SetTime.tm_year < TWO_DIGIT_YEAR_CUTOFF) { SetTime.tm_year += 100; } } // // Use the current year if no year was supplied. // } else { if ((Options & DATE_OPTION_UTC) != 0) { TimeComponents = gmtime(&Time); } else { TimeComponents = localtime(&Time); } if (TimeComponents == NULL) { Status = errno; SwPrintError(Status, NULL, "Failed to set time"); goto MainEnd; } SetTime.tm_year = TimeComponents->tm_year; } if (*Argument != '\0') { SwPrintError(0, Argument, "Unexpected garbage at end of line"); goto MainEnd; } // // The daylight savings of the supplied time are unknown. // SetTime.tm_isdst = -1; // // Interpret the parsed calendar time depending on the UTC option. If this // fails it will return -1 with errno set. Note that -1 without errno set // could be the result of a time that is 1 second before the Epoch. // if ((Options & DATE_OPTION_UTC) != 0) { Time = SwConvertGmtTime(&SetTime); } else { Time = mktime(&SetTime); } if ((Time == -1) && (errno != 0)) { Status = errno; SwPrintError(Status, Arguments[ArgumentIndex], "Invalid date"); goto MainEnd; } TimeValue.tv_sec = Time; TimeValue.tv_usec = 0; Status = SwSetTimeOfDay(&TimeValue, NULL); if (Status != 0) { Status = errno; SwPrintError(Status, NULL, "Failed to set time"); goto MainEnd; } // // Now print the updated time. The mktime and timegm routines modified the // parsed calendar time so that it is exactly what needs to be printed. // strftime(TimeBuffer, DATE_TIME_FORMAT_SIZE, DATE_SET_TIME_FORMAT, &SetTime); printf("%s\n", TimeBuffer); MainEnd: if (TimeBuffer != NULL) { free(TimeBuffer); } return Status; } // // --------------------------------------------------------- Internal Functions // INT DateParseComponent ( PSTR *String ) /*++ Routine Description: This routine pulls the next two digits out of the string and returns their decimal numerical representation. Arguments: String - Supplies a pointer that on input contains a pointer to a string. On output this value will be advanced. Return Value: Returns the integer on success. -1 on failure. --*/ { PSTR Argument; INT Value; Argument = *String; if ((!isdigit(*Argument)) || (!isdigit(*(Argument + 1)))) { return -1; } Value = ((*Argument - '0') * 10) + (*(Argument + 1) - '0'); Argument += 2; *String = Argument; return Value; }