1. Overview

Generating a list of dates in a range has many practical applications, such as organizing archives with date-specific labels, report generation and analysis, etc.

In this tutorial, we’ll learn how to generate a list of dates in a range in Bash using different command-line utilities.

2. Incrementing Date

Firstly, let’s see how we can get the current time in Linux using the date command:

$ date
Mon Aug 28 23:38:56 UTC 2023

Now, if we want to get the time 24 hours from now, we can use the –date option and specify “*+1 day*” as the increment value:

$ date --date "+1 day"
Tue Aug 29 23:39:24 UTC 2023

Lastly, we can even specify another date to which we can add the increment value:

$ date --date "2023-08-20 +1 day"
Mon Aug 21 00:00:00 UTC 2023

Great! We just learned an important concept that we’ll be able to use in different approaches to generate a list of dates in a range.

3. Using Loop Constructs With date

In this section, we’ll use loop constructs with the date command to solve our use case of generating a list of dates in a range.

3.1. With while Loop

First, let’s initialize the start_date and end_date variables to define the range of dates in YYYY-MM-DD format:

start_date="2023-08-01"
end_date="2023-08-07"

Next, let’s write a while loop to generate the list of dates within the specified range of dates:

while [[ "$start_date" != "$end_date" ]]; do
    echo "$start_date"
    start_date=$(date --date "$start_date + 1 day" +"%Y-%m-%d")
done

We must note that we’re incrementing the value of start_date by 1 day after each iteration. Additionally, we’ve used the +”%Y-%m-%d” as a format string so that start_date stores the date in the YYYY-MM-DD format.

Furthermore, we need to print the last date in the range because our range is inclusive of start_date and end_date:

echo "$end_date"

Lastly, let’s execute our code to see the list of dates in the range:

2023-08-01
2023-08-02
2023-08-03
2023-08-04
2023-08-05
2023-08-06
2023-08-07

Perfect! It looks like we got this one right.

3.2. With for Loop

Alternatively, we can also write a for loop to generate the list of dates. Additionally, let’s write our iteration logic using increment value in seconds instead of days.

First, let’s see that we can use an increment value of “*+86400 seconds*“ with the date command as a replacement for the “*+1 day*” increment value:

$ date
Tue Aug 29 22:19:29 UTC 2023
$ date --date "+86400 seconds" +"%Y-%m-%d"
2023-08-30

Now, let’s use the +%s format string to get the epoch value for the start_date and end_date in start_date_epoch and end_date_epoch variables, respectively:

$ start_date_epoch=$(date --date "$start_date" +%s)
$ echo $start_date_epoch
1690848000
$ end_date_epoch=$(date --date "$end_date" +%s)
$ echo $end_date_epoch
1691366400

Next, we can go ahead and write a for loop to add sec seconds to the start_date with a step value of 86400 for each iteration:

for (( sec=0; sec <= end_date_epoch - start_date_epoch; sec+=86400 )); do
    echo "$(date --date "$start_date + $sec seconds" +"%Y-%m-%d")"
done

It’s important to note that we’ve used double parentheses in the loop to perform the arithmetic operations.

Lastly, let’s execute our code and verify that it works as expected:

2023-08-01
2023-08-02
2023-08-03
2023-08-04
2023-08-05
2023-08-06
2023-08-07

The result looks correct.

4. Using printf and date

The printf command in Linux has native support to format and display dates. In this section, we’ll rely on printf to solve our use case.

First, let’s see how to use the format string “*%(%Y-%m-%d)T\n*” with the printf command for showing an epoch value in the YYYY-MM-DD format:

$ date -d "2023-08-01" +%s
1690848000
$ printf "%(%Y-%m-%d)T\n" "1690848000"
2023-08-01

Next, let’s initialize the current_date_epoch and end_date_epoch variables with epoch values for start_date and end_date, respectively:

current_date_epoch=$(date -d "$start_date" +%s)
end_date_epoch=$(date -d "$end_date" +%s)

Next, let’s go ahead and write a while loop to iterate over the current_date_epoch till it’s within the date range:

while [[ "$current_date_epoch" -le "$end_date_epoch" ]]; do
    printf "%(%Y-%m-%d)T\n" "$current_date_epoch"
    current_date_epoch=$((current_date_epoch + 86400))
done

Finally, let’s put our code in action and verify the results:

2023-08-01
2023-08-02
2023-08-03
2023-08-04
2023-08-05
2023-08-06
2023-08-07

As expected, we’ve got a list of all dates within the range.

5. Using seq and read

We can use the seq command to generate a sequence of numbers within a range using a fixed increment value:

$ seq <first_value> <increment> <last_value>

So, let’s go ahead and generate the sequence of epochs within the range using an increment value of 86400:

$ seq  $(date -d "$start_date" +%s) 86400 $(date -d "$end_date" +%s)
1690848000
1690934400
1691020800
1691107200
1691193600
1691280000
1691366400

Moving on, we can read this sequence using a while loop and show the corresponding date for each epoch value:

$ seq  $(date -d "$start_date" +%s) 86400 $(date -d "$end_date" +%s) |
while read -r epoch; do
    date --date "@$epoch" +"%Y-%m-%d"
done

We must note that we’re reading the value in the epoch variable and then showing its equivalent date value.

Like always, let’s confirm that our code is working correctly:

2023-08-01
2023-08-02
2023-08-03
2023-08-04
2023-08-05
2023-08-06
2023-08-07

The output looks correct.

6. Using seq and xargs

Instead of using the read command in a while loop, we can use the xargs command to make our code concise.

Let’s go ahead and use the -I option with {} as a placeholder to retrieve the date value for each epoch value:

$ seq $(date --date "$start_date" +%s) 86400 $(date --date "$end_date" +%s) | \
xargs -I {} date --date @{} +"%Y-%m-%d"
2023-08-01
2023-08-02
2023-08-03
2023-08-04
2023-08-05
2023-08-06
2023-08-07

That’s it! We got the correct output with improved code readability.

7. Using seq and sed

Alternatively, we can use the sed command with the -e flag to execute the date command for each epoch value in the sequence:

$ seq $(date --date "$start_date" +%s) 86400 $(date --date "$end_date" +%s) | \
sed -n -E -e 's/(.*)/date --date @& +"%Y-%m-%d"/ep'
2023-08-01
2023-08-02
2023-08-03
2023-08-04
2023-08-05
2023-08-06
2023-08-07

We’ve used & in the replacement string for passing the epoch value as an argument to the date command.

8. Using date and bc

In this section, we’ll use the bc utility in Linux to perform the arithmetic operation of managing the increment value.

First, let’s go ahead and install the bc utility using apt-get, as it’s not preinstalled:

$ apt-get install bc -y

Next, let’s initialize the start_date_epoch and end_date_epoch variables with the epoch values for the start_date and end_date:

start_date_epoch=$(date --date "$start_date" +%s)
end_date_epoch=$(date --date "$end_date" +%s)

Moving on, let’s write a while loop that increments start_date_epoch by 86400 after each iteration:

while [[ "$start_date_epoch" -le "$end_date_epoch" ]]; do
    date --date "@$start_date_epoch" +"%Y-%m-%d"
    start_date_epoch=$(echo "$start_date_epoch + 86400" | bc)
done

It’s important to note that we’ve used the bc command to do arithmetic sum calculations from a string expression.

Lastly, we must verify that the code is working as expected:

2023-08-01
2023-08-02
2023-08-03
2023-08-04
2023-08-05
2023-08-06
2023-08-07

It works!

9. Using awk

So far, we’ve used a combination of utilities to solve our use case. In this section, we’ll learn how to write a single awk script to get the list of dates in a range.

Let’s start by looking at the date_range.awk script:

$ cat date_range.awk
BEGIN {
    start_date_formatted=sprintf("%s %s", gensub(/-/, " ", "g", start), " 00 00 00")
    end_date_formatted=sprintf("%s %s", gensub(/-/, " ", "g", end), " 00 00 00")

    current = mktime(start_date_formatted)
    target = mktime(end_date_formatted)

    while (current <= target) {
        print strftime("%Y-%m-%d", current);
        current += 86400;
    }
}

Now, let’s break this down to understand the nitty gritty of the logic. Firstly, we must note that we’ve written the logic in the BEGIN block and intend to pass the start_date and end_date as the start and end parameters to the script.

Secondly, we used the gensub() function along with sprintf() function to format the dates so that they’re compatible with the mktime() function.

Lastly, we used the mktime() function to define the current and target dates and iterated over the current date while it’s less than or equal to the target date.

Further, let’s go ahead and execute the date_range.awk script:

$ awk -v start="$start_date" -v end="$end_date" -f date_range.awk /dev/null
2023-08-01
2023-08-02
2023-08-03
2023-08-04
2023-08-05
2023-08-06
2023-08-07

Here we specified /dev/null as the input file, as our code doesn’t need any input file. Additionally, we used the -v flag to pass start_date and end_date values as the start and end parameters.

10. Conclusion

In this article, we learned how to generate a list of dates in a range in Bash. Additionally, we explored multiple command-line utilities to solve the use case, such as date, awk, xargs, sed, printf, and bc.