4

In bash on macOS, I would like to write a small script with dates (or any other program that would do) that gives me a list of dates in the format yyyymmdd of every Saturday of a given year and saves it to a variable.

For example, if I wanted to have a list of dates for all Saturdays of the year 1850, it should somehow look like this:

var = [ 18500105, 18500112, 18500119, …, 18501228 ]

with the below code:

list=()
for month in `seq -w 1 12`; do
    for day in `seq -w 1 31`; do
    list=( $(gdate -d "1850$month$day" '+%A %Y%m%d' | grep 'Saturday' | egrep -o '[[:digit:]]{4}[[:digit:]]{2}[[:digit:]]{2}' | tee /dev/tty) )
    done
done

However, the above command does not write anything in the array list although it gives me the right output with tee.

How can I fix these issues?

RobC
  • 22,977
  • 20
  • 73
  • 80
Til Hund
  • 1,543
  • 5
  • 21
  • 37
  • 1
    On Linux you could do like this : `YEAR=2019; for m in $(seq -w 12); do for d in $(seq -w 31); do date "+%Y%m%d:%A" --date=$YEAR$m$d 2>/dev/null; done; done|grep Sat|cut -d: -f1`. Unfortunately I don't have access to MacOS. – Mickaël Bucas Mar 13 '19 at 09:55
  • 1
    Might be challenging because on macOS I encounter [years prior to 1901 are treated as invalid](https://unix.stackexchange.com/questions/7688/date-years-prior-to-1901-are-treated-as-invalid) when using `date`. – RobC Mar 13 '19 at 13:55
  • Hi, RobC, yes this problem can be solved using `gdate`. See updated code above. The remaining issue is that the array stays empty. :/ – Til Hund Mar 13 '19 at 13:56
  • 1
    Add to the `list` array, using `list+=(...)` _(note the `+`)_ – RobC Mar 13 '19 at 14:15
  • Yes, RobC, that solved my issue. Oh gosh, it took so long! – Til Hund Mar 13 '19 at 14:16
  • 1
    Til Hund - "it took so long" - yep that's programming sometimes ;) Out of interest, which version of macOS did your accepted answer run successfully on? – RobC Mar 13 '19 at 16:02
  • 1
    macOS 10.14.2, RobC. – Til Hund Mar 13 '19 at 16:03

3 Answers3

2

Argh, just realised you need it for MacOS date.

I will leave the answer for others that do not have that restriction, but it will not work for you.

This is not quite what you want, but close:

year=1850
firstsat=$(date -d $year-01-01-$(date -d $year-01-01 +%w)days+6days +%Y%m%d)
parset a 'date -d '$firstsat'+{=$_*=7=}days +%Y%m%d' ::: {0..52}
echo ${a[@]}

It has the bug, that it finds the next 53 Saturdays, and the last of those may not be in current year.

parset is part of GNU Parallel.

Ole Tange
  • 31,768
  • 5
  • 86
  • 104
  • Thank you, Ole Tange, for your answer. I hope it will help others. It is true, coming from Linux I feel often quite limited with the macOS terminal. :/ I updated my code above, how can I make grep add each date to a variable? – Til Hund Mar 13 '19 at 09:17
  • 1
    @TilHund Assuming your script outputs one date per line, it should be straightforward to turn that into an array: https://stackoverflow.com/questions/9449417/how-do-i-assign-the-output-of-a-command-into-an-array – Ruud Helderman Mar 13 '19 at 09:38
  • Hi Ruud Helderman, how can I use the array afterwards for another loop like `for i in "${arr[@]}"; do ...; done`? – Til Hund Mar 13 '19 at 10:37
  • 1
    @TilHund Your code does not append to `list`, it overwrites `list` upon each invocation of `grep`. Please read: https://stackoverflow.com/questions/1951506/add-a-new-element-to-an-array-without-specifying-the-index-in-bash – Ruud Helderman Mar 13 '19 at 14:26
  • Thank you all for pointing out this obvious mistake. I had it at some point in the code, but did not have list right in front of the entire command, but in the end with `grep`. Sry, I am a beginner. – Til Hund Mar 13 '19 at 14:42
2

Modifying Dennis Williamson's answer slightly to suit your requirement and to add the results into the array. Works on the GNU date and not on FreeBSD's version.

#!/usr/bin/env bash
y=1850

for d in {0..6}
do
    # Identify the first day of the year that is a Saturday and break out of
    # the loop
    if (( $(date -d "$y-1-1 + $d day" '+%u') == 6))
    then
        break
    fi
done

array=()
# Loop until the last day of the year, increment 7 days at a
# time and append the results to the array
for ((w = d; w <= $(date -d "$y-12-31" '+%j'); w += 7))
do
    array+=( $(date -d "$y-1-1 + $w day" '+%Y%m%d') )
done

Now you can just print the results as

printf '%s\n' "${array[@]}"

To set up the GNU date on MacOS you need to do brew install coreutils and access the command as gdate to distinguish it from the native version provided.

Inian
  • 80,270
  • 14
  • 142
  • 161
1

I didn't do much error checking, but here's another implementation.
Takes day of week and target year as arguments.

Gets the julian day of the first matching weekday requested - gets the epoch seconds of noon on that day - as long as the year matches what was requested, adds that date to the array and adds a week's worth of seconds to the tracking variable.

Lather, rinse, repeat until no longer in that year.

$: typeset -f alldays
alldays () { local dow=$1 year=$2 julian=1;
  until [[ "$dow" == "$( date +%a -d $year-01-0$julian )" ]]; do (( julian++ )); done;
  es=$( date +%s -d "12pm $year-01-0$julian" );
  allhits=( $( while [[ $year == "$( date +%Y -d @$es )" ]]; do date +%Y%m%d -d @$es; (( es+=604800 )); done; ) )
}
$: time alldays Sat 1850
real    0m9.931s
user    0m1.025s
sys     0m6.695s

$: printf "%s\n" "${allhits[@]}"
18500105
18500112
18500119
18500126
18500202
18500209
18500216
18500223
18500302
18500309
18500316
18500323
18500330
18500406
18500413
18500420
18500427
18500504
18500511
18500518
18500525
18500601
18500608
18500615
18500622
18500629
18500706
18500713
18500720
18500727
18500803
18500810
18500817
18500824
18500831
18500907
18500914
18500921
18500928
18501005
18501012
18501019
18501026
18501102
18501109
18501116
18501123
18501130
18501207
18501214
18501221
18501228
Paul Hodges
  • 13,382
  • 1
  • 17
  • 36
  • Still seems kinda crazy to me that this is taking 10s. Anyone have tips to speed it up? I know I could build some lookup table logic to make finding the initial occurance of the dow require only one `date` call. I suppose I could also just increment day of the the output by adding a week every time and checking if the month has changed if it goes over 28... but I was trying to keep the code small and readable. :( – Paul Hodges Mar 14 '19 at 13:27