Tyler Durden
Tyler Durden

Reputation: 645

Trouble with the mac date command

I am currently experiencing 2 problems with the mac date command.

I am running a shell script which first executes the command (ps -p $p_id -o etime=) to give me the elapsed time of a process with p_id, which returns a date of the form [[dd-]hh:]mm:ss

Problem 1: The following command to convert a date to another format seems to be causing weird problems: (date -j -f "%H:%M:%s" "$processStartTime" "+%H:%M:%s"). Say the prcoessStartTime=12:30:33. The output date this command returns is 01:00:33 even though I'm converting the date to the same format. It only gets the seconds right and the hours and minutes are wrong. If i use different quotation marks like '' surrounding the date format, i get even stranger results! The goal is to convert the time into minutes so i can see how many minutes the process has been running for.

Problem 2: Since the process date can be in the following format [[dd-]hh:]mm:ss how should i handle different date formats in my code? Should i have 3 conditions to check the format? or is there a way to handle this? i.e. some dates might be of the form hh:mm:ss and others mm:ss, and i need to have the input date format correct in the command otherwise it throws an error.

Upvotes: 0

Views: 1713

Answers (3)

carlo
carlo

Reputation: 37

To solve problem no. 1 you have to replace %s by %S (as already suggested by RBH):

processStartTime='12:30:33'
- date -j -f "%H:%M:%s" "$processStartTime" "+%H:%M:%s"
+ date -j -f "%H:%M:%S" "$processStartTime" "+%H:%M:%S"

Combining the ps ho lstart= $pid approach introduced by F. Hauri with setting LANG='C' it is possible to avoid problem no. 2 as well:

{
pid='1'
ps_date="$(LANG='C' ps ho lstart= $pid)"
# convert ps_date to Unix epoch time
processStartTimeEpochTime="$(LANG='C' date -j -f '%a %b %d %T %Y' "$ps_date" '+%s' 2>/dev/null)"
elapsed_secs="$(( $(date '+%s') - ${processStartTimeEpochTime} ))"
printf '%s\n' "ps_date: $ps_date" "processStartTimeEpochTime: $processStartTimeEpochTime"
printf '%s\n' "elapsed seconds: $elapsed_secs" "elapsed minutes: $(( $elapsed_secs / 60 ))" "elapsed hours: $(( $elapsed_secs / 3600 ))"
printf '%s' 'processStartTimeEpochTime reversed: '
date -r "$processStartTimeEpochTime" '+%F--%T'
}

If you insist on using ps -p $pid -o etime= it will get a bit clumsy because of parsing the [[dd-]hh:]mm:ss format.

{

pid='1'

# days, hours, minutes, seconds
# format: [[dd-]hh:]mm:ss
dhms="$(ps -p $pid -o etime=)"

days=""
h=""
m=""
s=""

get_hms() {
   hms="$1"
   h=${hms%%:*}
   hms=${hms#*:};
   m=${hms%%:*}
   hms=${hms#*:};
   s=${hms}
   return 0
}

case "$dhms" in
   ??-??:??:??) days=${dhms%%-*}; hms=${dhms#*-}; get_hms "$hms";;
   ?-??:??:??) days=${dhms%%-*}; hms=${dhms#*-}; get_hms "$hms";;
   ??:??:??) days="0"; hms="$dhms"; get_hms "$hms";;
   ?:??:??) days="0"; hms="0${dhms}"; get_hms "$hms";;
   ??:??) days="0"; hms="00:${dhms}"; get_hms "$hms";;
   ?:??) days="0"; hms="00:0${dhms}"; get_hms "$hms";;
   ??) days="0"; hms="00:00:${dhms}"; get_hms "$hms";;
   ?) days="0"; hms="00:00:0${dhms}"; get_hms "$hms";;
   *) days=""; hms="";;
esac

# strip a leading 0 if any
days="${days#0}"
h="${h#0}"
m="${m#0}"
s="${s#0}"

[ X"$days" = "X" ] && days='0'
[ X"$h" = "X" ] && h='0'
[ X"$m" = "X" ] && m='0'
[ X"$s" = "X" ] && s='0'

printf '%s\n' "d: $days" "h: $h" "m: $m" "s: $s"
days_in_secs=$(( $days * 86400 ))
hours_in_secs=$(( $h * 3600 ))
minutes_in_secs=$(( $m * 60 ))
time_span_in_secs=$(( $days_in_secs + $hours_in_secs + $minutes_in_secs + $s ))
processStartEpochTime="$(( $(date '+%s') - ${time_span_in_secs} ))"
printf '%s\n' "dhms: $dhms"
printf '%s' 'processStartEpochTime reversed: '
date -r "$processStartEpochTime" '+%F--%T'
printf '%s\n' "elapsed seconds: $time_span_in_secs" "elapsed minutes: $(( $time_span_in_secs / 60 ))" "elapsed hours: $(( $time_span_in_secs / 3600 ))"

}

Upvotes: 0

F. Hauri  - Give Up GitHub
F. Hauri - Give Up GitHub

Reputation: 70742

Two different way of string manipulation

Formating MaxOS's date dst format by using sed.

There is a strong sed command to re-place values for MacOs's date command (not very sexy, but it work):

date -j $(
  ps ho lstart= $p_id |
    sed '
      s/^....//;
      s/Jan/01/;s/Feb/02/;s/Mar/03/;s/Apr/04/;s/May/05/;s/Jun/06/;
      s/Jul/07/;s/Aug/08/;s/Sep/09/;s/Oct/10/;s/Nov/11/;s/Dec/12/;
      s/^\([0-9]\{2\}\) \([0-9]\{2\}\) \([0-9]\{2\}\):\([0-9]\{2\}\):\([0-9]\{2\}\) [0-9]\{2\}\([0-9]\{2\}\)/\1\2\3\4\6.\5/;
            ') +%s

This will render starting time of process $p_id in unixtimestamp.

Using for parsing date value

# Building translation variables for months strings
for ((i=1;i<13;i++));do
    val=$(printf %02d $i)
    mnt=$(date -j ${val}010101 +%b)
    eval mn$mnt=$val
  done

IFS=': ' read -a psdate < <(ps ho lstart= $p_id)
var=mn${psdate[1]}
printf -v fdte "%02d%02d%02d%02d%02d.%02d" ${!var} ${psdate[2]} \
     ${psdate[3]} ${psdate[4]} ${psdate[6]#??} ${psdate[5]}
date -j $fdte +%s

Both command was tested on MacOS. I find the second one more sexy.

Upvotes: 1

tripleee
tripleee

Reputation: 189327

You seem to be mixing apples and oranges.

The date command works with timestamps -- these are simple, atomic representations of a single discrete point in time, and as such, they refer to absolute time. In terms of implementation, a time_t is traditionally the number of seconds since midnight, January 1, 1970 UTC (on Unix).

By contrast, the timing information you are processing is relative and represents a time span.

If A is "now" and B is "one hour and thirty-three seconds", does it refer to a time starting at A-B and ending at A, or does it refer to a time starting at A and ending at A+B? Or is it relative to some other point in time? The date command conventionally interprets times without a date as relative to the preceding midnight; so you get (implicitly) a range starting at midnight and ending at the specified time.

The date command also has a parser for various human expressions of relative time, but it's necessarily inexact and unpredictable, because that's how human language is.

vbvntv$ date -d 'tomorrow 07:00'
Tue Nov 11 07:00:00 EET 2014

I don't have access to a Mac where I could test this, but generally, it seems to be less capable and featureful when it comes to parsing human dates.

In your situation, it's not clear what you really want to do. Perhaps you want to calculate when A-B, i.e. given a workload which finished now (A) and took B amount of time, when did it start?

A common way of doing that is to convert these to absolute seconds.

vbvntv$ date -d "00:00" 
Mon Nov 10 00:00:00 EET 2014

vbvntv$ date -d "00:00" +%s 
1415570400

vbvntv$ date -d "01:00:33" 
Mon Nov 10 01:00:33 EET 2014

vbvntv$ date -d "01:00:33" +%s
1415574033

vbvntv$ echo $((1415574033-1415570400))
3633

vbvntv$ date -d "3633 seconds ago"
Mon Nov 10 09:53:05 EET 2014

vbvntv$ # or alternatively

vbvntv$ date +%s
1415609708

vnvntv$ date -d @$((1415609708-3633))
Mon Nov 10 09:53:15 EET 2014

Upvotes: 0

Related Questions