Formatting date/time in the same way as "ls -l" command - c

I am implementing a version of ls -la command in OS X. I happened to notice that ls command lists years for some files and times for others as shown below:
-rwxr-xr-x 1 Jenna 197609 3584 Mar 13 2015 untitled1.exe*
drwxr-xr-x 1 Jenna 197609 0 Mar 2 07:48 Videos/
After some research, I found out that files older than 6 months gets a year while those that are not get the actual time. How do I get the date and times of these files to correctly display either the year or time ? Currently I have the following code which always displays the time:
int main()
{
struct stat *fileInfo;
char dir[] = "~/Desktop/file.txt";
stat(dir, &fileInfo);
printf("%.12s ", 4+ctime(&fileInfo->st_mtimespec));
return 0;
}

strftime is your friend here. Formatting it like ls is not that straight forward. The two formats that ls in coreutils is using are
"%b %e %Y"
"%b %e %H:%M"
Heres one paragraph from the docs of the ls in coreutils
/* TRANSLATORS: ls output needs to be aligned for ease of reading,
so be wary of using variable width fields from the locale.
Note %b is handled specially by ls and aligned correctly.
Note also that specifying a width as in %5b is erroneous as strftime
will count bytes rather than characters in multibyte locales. */
N_("%b %e %H:%M")
They're taking into consideration things like Locale ie multibyte characters etc. They also discuss things like when a file is older than N months they change how it's displayed etc. You can find a lot of information here...
http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c
For your needs something simpler might be fine, heres some code that might point you in the correct direction. You can change the format to whatever you want by changing the third argument to strftime.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main(void) {
time_t t;
srand((unsigned) time(&t));
size_t i = 0;
struct timespec ttime;
while(i < 0xffffffff) {\
ttime.tv_sec = i;
i += 3600;
struct tm time_struct;
localtime_r(&ttime.tv_sec, &time_struct);
char time_str[1024];
if(rand() > RAND_MAX/2) {
strftime(time_str, sizeof(time_str), "a == %b %e %Y", &time_struct);
}
else {
strftime(time_str, sizeof(time_str), "b == %b %e %H:%M", &time_struct);
}
printf("%s\n", time_str);
}
exit(0);
}
Try it and see what it does. From the man page, tweaking it will get you really close to what you need.
size_t strftime(char *restrict s, size_t maxsize, const char *restrict format, const struct tm *restrict timeptr);
The strftime() function formats the information from timeptr into the
buffer s, according to the string pointed to by format.

If I recall correctly, the determining factor used by ls -l to output a date/time or output a date/year is whether the mtime year returned by stat matches the current year. If the file mtime year matches the current year, then date/time information is provided, if the mtime year is not the current year, then date/year information is provided.
In order to make the comparison, you need to fill a struct tm for both the file mtime and now. The following example uses localtime_r to fill each struct tm, makes the comparison, and then outputs the file details accordingly.
(the code will read/compare mtimess and output the time format for all filenames provided as arguments. (a placeholder string is used to provide basic formatting for the file permissions, number, user, group, size, etc.)
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#define MAXC 64
void dump_tm (struct tm *t);
int main(int argc, char **argv)
{
int i = 0;
char time_str[MAXC] = "";
time_t now = time (NULL);
struct stat sb;
struct tm tmfile, tmnow;
if (argc < 2) { /* validate sufficient arguments provided */
fprintf (stderr, "error: insufficient input, usage: %s <pathname>\n",
argv[0]);
return 1;
}
for (i = 1; i < argc; i++) { /* for each file given as arg */
if (stat(argv[i], &sb) == -1) { /* validate stat of file */
perror("stat");
return 1;
}
localtime_r (&sb.st_mtime, &tmfile); /* get struct tm for file */
localtime_r (&now, &tmnow); /* and now */
if (tmfile.tm_year == tmnow.tm_year) { /* compare year values */
strftime (time_str, sizeof (time_str), "%b %e %H:%M",
&tmfile); /* if year is current output date/time */
printf ("permission 1 user group 12345 %s %s\n",
time_str, argv[i]);
}
else { /* if year is not current, output time/year */
strftime (time_str, sizeof (time_str), "%b %e %Y",
&tmfile);
printf ("permission 1 user group 12345 %s %s\n",
time_str, argv[i]);
}
}
return 0;
}
Example ls -l Output
$ ls -l tmp.c ls_time_date_fmt.c
-rw-r--r-- 1 david david 1312 Apr 6 03:15 ls_time_date_fmt.c
-rw-r--r-- 1 david david 2615 May 23 2014 tmp.c
Program Use/Output
$ ./bin/ls_time_date_fmt ls_time_date_fmt.c tmp.c
permission 1 user group 12345 Apr 6 03:15 ls_time_date_fmt.c
permission 1 user group 12345 May 23 2014 tmp.c
Let me know if you have any questions.

Related

Avoiding a newline when using fprintf with time.h

I keep getting a new line printed after the current time is written to a log.txt file. In this example I want the time and logMessage printed on the same line. Could you point me in the right direction as to what I'm missing here?
The logWrite function is called from the main function:
strcpy(logMessage, "**********RESTART**********");
logWrite(logMessagePtr);
The logWrite function is:
void logWrite(char * logMessagePtr)
{
/* Initialise time variables for log file. */
int hours, minutes, seconds, day, month, year;
// time_t is an arithmetic time type
time_t now;
// Update current time using time(&now);
time(&now);
FILE * logFilePtr;
logFilePtr = fopen ("C:\\log.txt", "a");
if (logFilePtr != NULL)
{
fprintf(logFilePtr, ("%s", ctime(&now)));
fprintf(logFilePtr, "%s", logMessagePtr);
fprintf(logFilePtr,"\n");
fclose(logFilePtr);
printf("Log file found\n"); //debug
}
else
{
perror("log.txt");
}
}
The log.txt file is sucessfully (created) appended to but I consistently get a new line feed after the time is written to the file.
When I run the program twice in a row for example, the log.txt reads:
Sat May 22 09:16:47 2021
**********RESTART**********
Sat May 22 09:16:48 2021
**********RESTART**********
What I want is:
Sat May 22 09:16:47 2021 **********RESTART**********
Sat May 22 09:16:48 2021 **********RESTART**********
Info on time.h
ctime(3) - Linux man page says:
The call ctime(t) is equivalent to asctime(localtime(t)). It converts the calendar time t into a null-terminated string of the form
"Wed Jun 30 21:49:08 1993\n"
It looks like adding newline character is included in the specification of ctime(). You should use strftime() to manually format the date and time without newline.
char date[128];
strftime(date, sizeof(date), "%a %b %d %T %Y", localtime(&now));
fprintf(logFilePtr, "%s", date);
The ctime function always adds '\n' to the end of string. There are many different ways to solve the problem, but the easiest way is to remove this symbol. This can be done, for example, like this (based on the fact that the ctime (see man) returns char *, not const char *):
#include <string.h>
...
char *ctime_line = ctime (&now);
ctime_line[strlen (ctime_line) - 1] = '\0';
and then print this line:
fprintf (logFilePtr, "%s", ctime_line);
P.S. I do not recommend using a record like this, since the string AAA may contain, for example, the % character, which will be interpreted as a special character.
// bad
fprintf (logFilePtr, logMessagePtr);
// good
fprintf (logFilePtr, "%s", logMessagePtr);
P.P.S. The following three lines
fprintf (logFilePtr, "%s", ctime (&now));
fprintf (logFilePtr, "%s", logMessagePtr);
fprintf (logFilePtr, "\n");
can be combined into one
fprintf (logFilePtr, "%s %s\n", ctime (&now), logMessagePtr);
As per documentation of ctime, the string has a newline embedded. You need to remove it from consideration for printing.
Since the string returned by ctime has a specified format of constant width, this should accomplish that:
fprintf(logFilePtr, "%.24s ", ctime(&now));
(edit: This is assuming you wish to use ctime in the first place, as you do in the question. There are other, potentially better, options, such as strftime.)

Convert date to epoch time ignoring system timezone

I have written the following code to convert date to timestamp.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
int main()
{
struct tm date_time;
char date_time_buf[255];
char date_time_hdr[255]={0};
strncpy(date_time_hdr,"Thu, 02 Mar 2017 05:54:28 GMT",255);
memset(&date_time, 0, sizeof(struct tm));
strptime(date_time_hdr, "%a, %d %b %Y %H:%M:%S %Z", &date_time);
memset(date_time_buf, 0, 255);
strftime(date_time_buf, 255, "%s", &date_time);
int p=atoi(date_time_buf);
printf("time is %d \r\n", p);
return 0;
}
I am able to convert date to timestamp. But facing an issue.
The timestamp is offset by 5 hrs 30 minutes which is the timezone of my linux machine. But I don't want that. Is there a way to ignore system timezone?
Instead of using strftime() to format the broken down time struct tm as an integer, and parsing it using atoi(), you can use the simple but nonstandard timegm() function.
Even though timegm() is not in POSIX, it is provided by most POSIXy systems. If you want your code to be portable across POSIXy systems, you can use a workaround (as described in some versions of the timegm man page):
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
time_t alt_timegm(struct tm *from)
{
char *tz;
time_t result;
int saved_errno;
tz = getenv("TZ");
if (tz)
tz = strdup(tz);
setenv("TZ", "", 1); /* TZ empty refers to UTC */
tzset();
errno = 0;
result = mktime(from);
saved_errno = errno;
setenv("TZ", tz, 1);
free(tz);
errno = saved_errno;
return result;
}
This workaround does have the drawback that it temporarily modifies the current timezone, which affects other threads executing any timezone-related functions. In a single-threaded process this is not an issue (as the timezone-related functions are not async-signal safe, so they aren't to be used in signal handlers).
In multithreaded programs, one should serialize all accesses to the timezone related functions -- alt_timegm() above, mktime(), localtime(), and so on --, to ensure each function is run with the proper timezone set.
The timegm() implementations in Linux and BSDs are thread-safe; that is, they do not modify the timezone.
Using the above, parsing a string using an strptime() format to an Unix timestamp is easy:
const char *parse_utc(const char *s, const char *format, time_t *to)
{
char *result;
struct tm t_parts;
time_t t;
if (!s || !format) {
errno = EINVAL;
return NULL;
}
result = strptime(s, format, &t_parts);
if (!result) {
errno = EINVAL;
return NULL;
}
errno = 0;
t = alt_timegm(&t_parts);
if (to)
*to = result;
return (const char *)result;
}
The above parse_utc() parses a string s using strptime() format format, saving the UTC timestamp to to if not NULL, returning the pointer to the first unparsed character in string s.
Setting errno = 0 in the above functions may look strange, but it is because the mktime() (and timegm()) function may or may not set errno in case of an error; it just returns (time_t)-1. This means that it is impossible to reliably determine whether a (time_t)-1 (corresponding to last second of year 1969 in UTC`) is an actual valid timestamp, or an error return.
In other words, if errno != 0 after a call to parse_utc(), there was an error. (Note that if the string or format was invalid, parse_utc() returns NULL, with errno == EINVAL.) If errno == 0 but *to == (time_t)-1, it depends on the architecture whether the time string actually referred to the last second of year 1969 in UTC, or if it was an error; we simply do not know.

How to get user's last login including the year in C

I'm trying to write a program that can get the user's last login time. Then I want to do something with the data. E.g. check which user haven't logged in for 90 days.
So I tried using last command, this works great. Just that it doesn't display the year. So if I would do the checking on January 2014, how would I know the last login is actually would be on Dec 2013 or Dec 2012?
This is an example of the code:
FILE *file1;
extern FILE *popen();
char command[1024];
char *username;
char result[1024];
char buff[1024];
username = "fikrie";
sprintf(command,"last %s",username);
file1 = popen(command, "r");
while(fgets(result, sizeof(result), file1)!=NULL){
sprintf(buff,"%s",result);
}
pclose(file1);
After some googling, I found out there's a -F flag to display the year. Unfortunately, it doesn't work on HP-UX, Solaris. These platform doesn't recognize that flag.
I've also tested to used utmp based from this link here .The problem is this code will fail on Solaris and HP-UX. Seems like ut_tv doesn't exist in utmp struct.
#include <stdio.h>
#include <utmp.h>
#include <string.h>
#include <time.h>
int main(void) {
struct utmp *line;
time_t timestamp;
utmpname("/var/log/wtmp");
setutent();
while( (line = getutent()) != NULL) {
if (line->ut_type == USER_PROCESS ) {
timestamp = line->ut_tv.tv_sec;
printf("%s %s", line->ut_user, asctime(localtime(&timestamp)));
}
}
endutent();
return 0;
}
So, is there a way to do a C code that can generally get the last login for all the Unix platform. (Solaris, Redhat/Linux/CentOS, AIX, HP-UX, Ubuntu).
Seems like when I manage to get it on 1 platform, then it fails on the others.

system time setting using c library function

I can get the system time using struct tm and time(),localtime(),asctime().but i need help about how i can set time of system using c program.
if you don't want to execute a shell command you can (as you mentioned) use the settimeofday, i would start by reading the MAN page, or looking for some examples
here's an example:
#include <sys/time.h>
#include <stdio.h>
#include <errno.h>
int main(int argc, char *argv[])
{
struct timeval now;
int rc;
now.tv_sec=866208142;
now.tv_usec=290944;
rc=settimeofday(&now, NULL);
if(rc==0) {
printf("settimeofday() successful.\n");
}
else {
printf("settimeofday() failed, "
"errno = %d\n",errno);
return -1;
}
return 0;
}
Shamelessly ripped From IBMs documentation, the struct timeval struct holds the number of seconds (as a long) plus the number of microseconds (as a long) from 1 January 1970, 00:00:00 UTC (Unix Epoch time). So you will need to calculate these numbers in order to set the time. you can use these helper functions, to better handle dealing with the timeval struct.

How to programatically convert a time from one timezone to another in C?

The info pages for the GNU date command contains this example:
For example, with the GNU date
command you can answer the question
"What time is it in New York when a
Paris clock shows 6:30am on October
31, 2004?" by using a date beginning
with `TZ="Europe/Paris"' as shown in
the following shell transcript:
$ export TZ="America/New_York"
$ date --date='TZ="Europe/Paris" 2004-10-31 06:30'
Sun Oct 31 01:30:00 EDT 2004
In this example, the '--date'
operand begins with its own 'TZ'
setting, so the rest of that operand
is processed according to
'Europe/Paris' rules, treating the
string 2004-10-31 06:30 as if it
were in Paris. However, since the
output of the date command is
processed according to the overall
time zone rules, it uses New York
time. (Paris was normally six hours
ahead of New York in 2004, but this
example refers to a brief Halloween
period when the gap was five hours.)
I am trying to accomplish essentially the same thing programatically in C without calling the date program millions of times. Basically I am looking for a way to take an arbitrary date and time in one timezone and convert it to the equivalent date and time in another timezone either directly or via conversion to and from UTC. I don't care about the formats of the input and output time as long as I can manipulate them using standard functions (strftime/strptime/mktime/etc).
The date program appears to accomplish this using complex routines internal to the coreutils package, I am looking for a way to do this in C using either standard POSIX/Linux routines or an external library. I looked at zoneinfo quite a bit which seemed promising but I cannot find any libraries to do anything useful with it.
I came up with a solution that seems to work on Linux with glibc, I don't know how portable this behavior is, any comments about portability or a better way to go about this would be welcome.
convert_time.c (No error checking for clarity):
#define _XOPEN_SOURCE
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
int main (int argc, char *argv[])
{
struct tm mytm = {0};
time_t mytime;
char buf[100];
mytm.tm_isdst = -1;
putenv(argv[1]);
tzset();
strptime(argv[2], "%Y-%m-%d %H:%M", &mytm);
mytime = mktime(&mytm);
putenv(argv[3]);
tzset();
localtime_r(&mytime, &mytm);
strftime(buf, 100, "%a, %d %b %Y %H:%M:%S %z(%Z)", &mytm);
puts(buf);
return 0;
}
The first argument is the source timezone (actually "TZ=Timezone" to pass to putenv), the second argument is the time in the specified format, the last argument is the destination timezone. I am using zoneinfo timezone names which glibc supports using the zoneinfo database.
The results of several DST test corner cases correspond to the results from the equivalent date command as well as this site which uses the zoneinfo database:
$ ./convert_time "TZ=America/New_York" "2005-05-31 06:30" "TZ=America/Indiana/Indianapolis"
Tue, 31 May 2005 05:30:00 -0500(EST)
$ ./convert_time "TZ=America/New_York" "2006-05-31 06:30" "TZ=America/Indiana/Indianapolis"
Wed, 31 May 2006 06:30:00 -0400(EDT)
$ ./convert_time "TZ=Europe/Paris" "2004-10-30 06:30" "TZ=America/New_York"
Sat, 30 Oct 2004 00:30:00 -0400(EDT)
$ ./convert_time "TZ=Europe/Paris" "2004-10-31 06:30" "TZ=America/New_York"
Sun, 31 Oct 2004 01:30:00 -0400(EDT)
$ ./convert_time "TZ=Europe/Paris" "2004-11-01 06:30" "TZ=America/New_York"
Mon, 01 Nov 2004 00:30:00 -0500(EST)
For time:
Get the GMT time with gmtime
Add/subtract the hours from time_t.tm_hour
Use mktime to renormalize
The date calculation will be similar but a little more complicated.
I was actually looking for the same answer you did, and this is the solution I came up with.
The following sample program takes the local system time and converts it to any timezone you want.
// compile: gcc snippet.c
// usage: ./a.out Europe/Berlin Pacific/Nauru Asia/Hong_Kong Asia/Kabul
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
int main(int argc, char *argv[])
{
char buf[80];
time_t tt = time(NULL);
struct tm tm;
int i;
if (!localtime_r(&tt, &tm)) {
printf("Could not convert time_t to tm struct. %s\n", strerror(errno));
return 1;
}
printf("time_t: %ld\n", tt);
printf("timezone: %ld, %s/%s, daylight:%d\n", timezone, tzname[0], tzname[1], daylight);
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %z %Z", &tm);
printf("time: %s\n", buf);
for (i = 1; i < argc; ++i) {
printf("\ninput: %s\n", argv[i]);
snprintf(buf, sizeof(buf), "TZ=%s", argv[i]);
putenv(buf);
tzset();
printf("\ttimezone: %ld, %s/%s, daylight:%d\n", timezone, tzname[0], tzname[1], daylight);
if (!localtime_r(&tt, &tm)) {
printf("Could not convert time_t to tm struct. %s\n", strerror(errno));
return 1;
}
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %z %Z", &tm);
printf("\ttime: %s\n", buf);
}
return 0;
}

Resources