0

This is a follow-up question to a previous question regarding a bash script I wrote to help me more accurately keep track of my vices. I'm now trying to modify the script to display all non-zero units:

It's been 2 weeks, 1 day and 5 hours since you last bought a deck.

To that end here are the relevant parts of the script I have so far:

last_bought=$(cat "$lb_file") # file contains time in epoch seconds
...
now=$(date -u +%s)
elapsed="$((now-last_bought))"
W=$((elapsed/60/60/24/7))
D=$((elapsed/60/60/24))
H=$((elapsed/60/60%24))
if [[ $W -le 0 && $D -le 0 && $H -gt 0 ]]; then string="$H hours"
elif [[ $W -le 0 && $D -gt 0 && $H -gt 0 ]]; then string="$D days and $H hours"
elif [[ $W -gt 0 && $D -gt 0 && $H -le 0 ]]; then string="$W weeks and $D days"
elif [[ $W -gt 0 && $D -le 0 && $H -gt 0 ]]; then string="$W weeks and $H hours"
elif [[ $W -gt 0 && $D -gt 0 && $H -gt 0 ]]; then string="$W weeks, $D days and $H hours"
fi

This works perfectly for hours and days (running the script with an hours value of -b 50 shows 2 days and 2 hours). However, when an hour value is entered into the script that is at least one week, both the $W and $D variables are populated. This means that for a value of -b 168 (1 week in hours), the output will be:

It's been 1 weeks and 7 days since you last bought a deck.

How can I fix this, preferably while also implementing singular periods of time like so:

It's been 1 week and 1 hour since you last bought a deck.

I'm also aware that I'm probably checking my logic in a very crude way, so if there's a smarter/more common way to do so I'd appreciate being enlightened, and

Hashim Aziz
  • 11,898
  • 35
  • 98
  • 166
  • Your calculation is weird. First calculate weeks, then substract the weeks value from elapsed. Use the remaining elapsed time to calculate days. Substract the days from the elapsed time too. Use the remain elapsed time to calculate hours. That is the normal way to do this kind of thing. – Tonny Jun 01 '21 at 15:12

3 Answers3

1
now=$(date +%s)
last_bought=$(date -d '-1 week -3 days -6 hours -34 minutes' +%s)

elapsed=$(( (now - last_bought) / 3600))     # truncate to hours
((hrs   = elapsed % 24, elapsed /= 24))
((days  = elapsed % 7))
((weeks = elapsed / 7))
echo "$weeks weeks, $days days, $hrs hours"
1 weeks, 3 days, 6 hours
Hashim Aziz
  • 11,898
  • 35
  • 98
  • 166
glenn jackman
  • 25,463
  • 6
  • 46
  • 69
  • Thank you for being the only comment or answer to show the code, but as far as I can tell all this does is spit back out the numbers that you've manually passed to `date`, so I'm confused as to how it answers my question. – Hashim Aziz Jun 01 '21 at 17:33
  • I think your confusion might come from the fact that I didn't include the source of the `last_bought` variable, which was my bad. It is defined in seconds since epoch, like this: `last_bought=$(cat "$lb_file")`, where the content of `$lb_file` is a time in seconds since epoch. – Hashim Aziz Jun 01 '21 at 17:52
  • What I demonstrate here is how the duration between $now (in seconds) and $last_bought (in seconds) can be calculated. The fact that the numbers emitted are the same proves it works. – glenn jackman Jun 01 '21 at 18:53
  • 1
    @glennjackman not very well versed in bash math, can you clarify? for the hours, `/=` truncates to int (not rounded) and then assigns to elapsed; is the comma list evaluated right-to-left? – Yorik Jun 01 '21 at 19:22
  • @glennjackman I see what you're saying now - strange though that I can't get it to work. With `-set ex` enabled and a value of `-b 170`, the last line executed before exit is `(( days = elapsed % 7 ))`, so it seems it's failing at that point. – Hashim Aziz Jun 01 '21 at 19:25
  • I managed to fix that issue by fixing what I assume were syntax errors in the above code, as with these fixes the script no longer fails on the variable assignment lines. However, even after fixing those there was still a problem with the hours variable: `-b 72` gave *3 days and 3 hours* (should be 0 hours), and `-b 170` outputs *1 week and 7 hours* (should be 2 hours). For that matter `-b 168` should have been exactly 1 week but also gave *1 week and 7 hours*. – Hashim Aziz Jun 01 '21 at 23:25
  • What is `-b`? Is it an option to your script? I don't see it in your code. How does the `-b` number relate to `last_bought`? – glenn jackman Jun 01 '21 at 23:38
  • Are you executing the script with `bash` or `sh`? – glenn jackman Jun 01 '21 at 23:39
  • @Yorik, `help let` shows you the bash arithmetic operators. See also [Arithmetic Expansion](https://www.gnu.org/software/bash/manual/bash.html#Arithmetic-Expansion) and [Shell Arithmetic](https://www.gnu.org/software/bash/manual/bash.html#Shell-Arithmetic) in the manual. – glenn jackman Jun 01 '21 at 23:42
  • I did that before I asked. It says nothing about truncation. – Yorik Jun 02 '21 at 14:08
  • bash can only do integer arithmetic. For floating point, you need to call out to some other tool (`bc` is common) – glenn jackman Jun 02 '21 at 14:24
0

At first glance, I think the problem is that you are evaluating W,D,H using the full elapsed time.

After each evaluation, you should decrement elapsed by that amount.

The case where day and hour appears to work without decrementing is most likely because H is evaluated using modulo 24.

Also bear in mind that date operations are more complex because of leap years, months, daylight savings, calendar rollover etc. etc.

From the snippet and apparent use, the math it is probably good enough though.

Yorik
  • 4,166
  • 1
  • 11
  • 16
0

After a lot of trial and error and looking up the documentation for bash's very confusing variable operators (=/, =* and the like), I managed to adapt Glenn's answer enough to get it to work for me:

now=$(date +%s)
last_bought=$(date -u +%s)
elapsed=$(((now-last_bought) / 3600))
H=$((elapsed % 24))
D=$(( ( elapsed /= 24 ) % 7))
W=$((elapsed / 7))
echo "$H weeks, $D days, $H hours"

I'm still not entirely sure I understand how the variable operators work, but either way the code now seems to be working as I wanted.

Hashim Aziz
  • 11,898
  • 35
  • 98
  • 166