1

I want to create a command that is called Age which calls this script:

From=$(stat / | grep "Birth" | sed 's/Birth: //g' | cut -b 2-11)
echo $From
Now=$(date)
echo $Now

However, I'm stuck subtracting these two dates.

How can I subtract them and ensure that it does not mess with date times like 2022-05-07 with 2022-07-05? I mean, which is the month and which is the date?

I think I should change the format of stat command to be more descriptive to prevent that confusion, then make sure that both dates are in the same format, then subtract them. But I can't find how.

  • in RHEL 7.9 for stat / I get Birth: - confused, don't know about stat but interested in finding date of linux installation. – ron Jan 23 '23 at 15:50
  • if you have two dates, output from date command or something similar, you would want to convert that to epoch time which will be a long int of seconds since 1970 (way back) then do simple subtraction to get elapsed time, then scale seconds to number of days. The C library time.h has those functions – ron Jan 23 '23 at 15:53
  • https://stackoverflow.com/questions/13932909/difference-between-two-dates-in-c – ron Jan 23 '23 at 15:56
  • https://stackoverflow.com/questions/9008824/how-do-i-get-the-difference-between-two-dates-under-bash – ron Jan 23 '23 at 15:58
  • the output of stat, or whatever, will be consistent it won't arbitrarily change the position of what month and day is. So you just capture the values of year, month, day and then things will work out accordingly. It's up to you for whatever date, stat, time commands you use to interpret their output correctly.... then year is year, month is month, day is day, and so on, no ambiguity. – ron Jan 23 '23 at 16:01
  • this -> answer in bash -> https://askubuntu.com/questions/1158870/how-to-subtract-two-time-stamps-along-with-their-dates-in-bash – ron Jan 23 '23 at 16:04
  • @ron : Regarding your first comment, please read my last comment in Stéphane Chazelas' answer. – MC68020 Jan 24 '23 at 15:10

2 Answers2

3

This answer is wrong!!! The difference should be calculated manually from the number of seconds. Displaying with date command just displays month, day number of the day, which is so many seconds from 1970-01-01.


I would do it like this:

#!/bin/bash

d1=$(date -d "$(stat / | grep "Birth" | sed 's/Birth: //g')" +%s) d2=$(date +%s) date -d "@$(($d2 - $d1))" +'%m months, %d days, %H hours'

Get both times in seconds. Subtract them and display them in a format of your choice. In this case it is in months, days and hours. You can change that to your preferences. Try man date to see what options you have for output format.

Regarding confusion in date format YYYY-MM-DD, I have never seen that computer would interpret that wrongly as YYYY-DD-MM.


Addition based on muru's comment (oneliner):

date -d "@$(($(date +%s) - $(stat / --format %W)))" +'%m months, %d days, %H hours'

Addition after @Saeed Neamati comment. The following answers provide much better solutions. They are still not 100% correct.

First option If you calculate difference and then add that difference to 1970-01-01, you can display the difference in years, months and day from 1970-01-01. Because months differ in length and because of leap years the result is not exact:

seconds=$(date -d "@$(($(date +%s) - $(stat / --format %W)))" +%s)
years=$(($(date -d @$seconds +%Y) - 1970))
months=$(($(date -d @$seconds +%m) - 1))
days=$(($(date -d @$seconds +%d)))
echo $years years, $months months, $days days

Second option Another way is to get both dates and calculate the difference between them. Days may not be correct, because not all months are 31 days long:

d1_year=$(date -d @$(stat / --format %W) +%Y)
d1_month=$(date -d @$(stat / --format %W) +%-m)
d1_day=$(date -d @$(stat / --format %W) +%-d)

d2_year=$(date +%Y) d2_month=$(date +%-m) d2_day=$(date +%-d)

years=$(($d2_year - $d1_year)) months=$(($d2_month - $d1_month)) if (( $months < 0 )); then months=$(($months + 12)) years=$(($years - 1)) fi days=$((d2_day - $d1_day)) if (( $days < 0 )); then days=$(($days + 31)) months=$((months -1)) fi

echo $years years, $months months, $days days

nobody
  • 1,710
  • 4
    You don't have to do all that to get the creation time in Unix epoch: stat --format %W should be enough – muru Jan 15 '23 at 10:45
  • Shouldn't returned value be checked before substracting ? (on my system, for instance, stat / would say : " Birth: -" (Not to say BTW that calculating "the age of a Linux install" from the creation time of the root dir is a quick & dirty bias.) – MC68020 Jan 15 '23 at 14:04
  • @nobody, this works. But I installed my machine yesterday, and today it shows me that it's installed for 1 month and 1 day. It has a small bug that I can't find. It should show me 0 months and 1 days – Saeed Neamati Jan 23 '23 at 09:28
  • @Saeed Neamati You are right. date -d "1971-01-01 00:00:00" +'%m months, %d days, %H hours' prints 01 months, 01 days, 00 hours instead of 12 months, 01 days, 00 hours". That is because the difference is added to1970-01-01 00:00:00' and then month and ay number of the added day are printed, which is wrong. – nobody Jan 23 '23 at 13:17
1

2022-05-07, contrary to 05/07/22 or 07/05/2022 is an unambiguous standard date format that is always YYYY-MM-DD. However, here, you don't need to parse a calendar datetime, you can get the raw data as a number of seconds since the epoch.

With GNU stat (recent enough and with a recent glibc and Linux kernel so it reports the birth time) and the ddiff command from dateutils (dateutils.ddiff in Debian and derivatives):

ddiff -i %s -f %d "$(stat -c%W /)" now

Or making the approximation that one day is 86400 seconds (they are 86400 Unix seconds, but the calculation may be off by one day on rare occasions due to DST in some timezones):

echo "$(( ($(date +%s) - $(stat -c%W /)) / 86400 ))"

With zsh after zmodload zsh/datetime or with recent versions of bash, $(date +%s) can be replaced with $EPOCHSECONDS.

ts from moreutils, with -r, can parse timestamps and convert them to some 61d25m ago / 166d7h ago format. It recognises a number of timestamp formats including ISO-8600 one, so you'd need to transform stat's output slightly, and get the UTC time as I don't think ts interprets the timezone part:

TZ=UTC0 stat -c%w / |
  sed 's/ /T/;s/ .*/Z/' |
  ts -r

Or use GNU find instead of stat with TZ=UTC0 find / -prune -printf '%BFT%BTZ\n' to get the timestamp in the right format from the start (again, needs a very recent version of findutils, glibc, Linux).

  • Sorry Stéphane but I am afraid that this is still not reliable enough since, as per my comment in other answer, stat -c%W / will just ouptut 0! (Despite using not that old yet I think stat 9.1 / glibc 2.36 / linux 5.4.225 ) – MC68020 Jan 23 '23 at 17:11
  • @MC68020, what's the type of your root filesystem? – Stéphane Chazelas Jan 23 '23 at 17:25
  • Ext4 - reported by dumpe2fs created on Thu Apr 28 17:19:11 2022. (with initial content filled by some rsync.) The only originality I can think of is that it is still being with 128 bytes inodes. But I do not think that matters since stat-ing /tmp which is a tmpfs does not report any birth date as well. – MC68020 Jan 23 '23 at 17:51
  • Digged a little more into Ext4 inode's layout and since i_crtime stands at offset 0x90 (=144>128), birth date won't (normally) be presented in case of 128 bytes inodes. No problem since I do not need that info. But you could probably inform as part of your answer that it only works with > 128 bytes inodes. – MC68020 Jan 23 '23 at 19:13
  • 1
    @MC68020, I see mke2fs(8) mentions an inode size of 128 bytes do not support timestamps beyond January 19, 2038, but not the fact that the crtime is also lost. Good find. I've added a note in the linked answer. This one is more about doing calculations on the birth time as reported by stat. – Stéphane Chazelas Jan 23 '23 at 19:29