Как создать последовательность дат с указанием даты начала и окончания, используя AWK сценариев BASH? - PullRequest
20 голосов
/ 04 декабря 2010

У меня есть набор данных в следующем формате

В первом и втором полях указаны даты (M / D / YYYY) начала и окончания исследования.

Как расширить данные в желаемый выходной формат, учитывая високосные годы с использованием сценариев AWK или BASH?

Ваша помощь очень ценится.

Ввод

  7/2/2009   7/7/2009
  2/28/1996  3/3/1996
  12/30/2001 1/4/2002

Желаемый выход

  7/7/2009
  7/6/2009
  7/5/2009
  7/4/2009
  7/3/2009
  7/2/2009
  3/3/1996
  3/2/1996
  3/1/1996
  2/29/1996
  2/28/1996
  1/4/2002
  1/3/2002
  1/2/2002
  1/1/2002
  12/31/2001
  12/30/2001

Ответы [ 5 ]

57 голосов
/ 07 мая 2014

Это можно сделать только с помощью bash:

for i in `seq 1 5`;
do
  date -d "2017-12-01 $i days" +%Y-%m-%d;
done;

или с трубами:

seq 1 5 | xargs -I {} date -d "2017-12-01 {} days" +%Y-%m-%d
13 голосов
/ 04 декабря 2010

Если у вас есть gawk:

#!/usr/bin/gawk -f
{
    split($1,s,"/")
    split($2,e,"/")
    st=mktime(s[3] " " s[1] " " s[2] " 0 0 0")
    et=mktime(e[3] " " e[1] " " e[2] " 0 0 0")
    for (i=et;i>=st;i-=60*60*24) print strftime("%m/%d/%Y",i)
}

Демонстрация:

./daterange.awk inputfile

Выход:

07/07/2009
07/06/2009
07/05/2009
07/04/2009
07/03/2009
07/02/2009
03/03/1996
03/02/1996
03/01/1996
02/29/1996
02/28/1996
01/04/2002
01/03/2002
01/02/2002
01/01/2002
12/31/2001
12/30/2001

Edit:

Сценарий, приведенный выше, страдает наивным предположением о продолжительности дней. Это мелкая гнида, но она может дать неожиданные результаты при некоторых обстоятельствах. По крайней мере, еще один ответ здесь также имеет эту проблему. Предположительно, команда date с вычитанием (или сложением) количества дней не имеет этой проблемы.

В некоторых ответах требуется заранее знать количество дней.

Вот еще один метод, который, мы надеемся, решит эти проблемы:

while read -r d1 d2
do
    t1=$(date -d "$d1 12:00 PM" +%s)
    t2=$(date -d "$d2 12:00 PM" +%s)
    if ((t2 > t1)) # swap times/dates if needed
    then
        temp_t=$t1; temp_d=$d1
        t1=$t2;     d1=$d2
        t2=$temp_t; d2=$temp_d
    fi
    t3=$t1
    days=0
    while ((t3 > t2))
    do
        read -r -u 3 d3 t3 3<<< "$(date -d "$d1 12:00 PM - $days days" '+%m/%d/%Y %s')"
        ((++days))
        echo "$d3"
    done
done < inputfile
10 голосов
/ 04 декабря 2010

Вы можете сделать это в оболочке без awk, при условии, что у вас есть дата GNU (которая необходима для формы date -d @nnn и, возможно, возможность разбивать ведущие нули на однозначные дни и месяцы):

while read start end ; do
    for d in $(seq $(date +%s -d $end) -86400 $(date +%s -d $start)) ; do
        date +%-m/%-d/%Y -d @$d
    done
done

Если вы находитесь в локали, которая делает переход на летнее время, то это может испортиться, если вы запросите последовательность дат, когда между ними происходит переход на летнее время. Используйте -u для принудительной установки UTC, которая также строго соблюдает 86400 секунд в день. Как это:

while read start end ; do
    for d in $(seq $(date -u +%s -d $end) -86400 $(date -u +%s -d $start)) ; do
        date -u +%-m/%-d/%Y -d @$d
    done
done

Просто введите этот код на стандартный ввод.

Вывод для ваших данных:

7/7/2009
7/6/2009
7/5/2009
7/4/2009
7/3/2009
7/2/2009
3/3/1996
3/2/1996
3/1/1996
2/29/1996
2/28/1996
1/4/2002
1/3/2002
1/2/2002
1/1/2002
12/31/2001
12/30/2001
3 голосов
/ 04 декабря 2010

Я предпочитаю даты формата ISO 8601 - вот решение, использующее их.Вы можете легко адаптировать его к американскому формату, если хотите.

Скрипт AWK

BEGIN {
    days[ 1] = 31; days[ 2] = 28; days[ 3] = 31;
    days[ 4] = 30; days[ 5] = 31; days[ 6] = 30;
    days[ 7] = 31; days[ 8] = 31; days[ 9] = 30;
    days[10] = 31; days[11] = 30; days[12] = 31;
}
function leap(y){
    return ((y %4) == 0 && (y % 100 != 0 || y % 400 == 0));
}
function last(m, l,  d){
    d = days[m] + (m == 2) * l;
    return d;
}
function prev_day(date,   y, m, d){
    y = substr(date, 1, 4)
    m = substr(date, 6, 2)
    d = substr(date, 9, 2)
    #print d "/" m "/" y
    if (d+0 == 1 && m+0 == 1){
        d = 31; m = 12; y--;
    }
    else if (d+0 == 1){
        m--; d = last(m, leap(y));
    }
    else
        d--
    return sprintf("%04d-%02d-%02d", y, m, d);
}
{
    d1 = $1; d2 = $2;
    print d2;
    while (d2 != d1){
        d2 = prev_day(d2);
        print d2;
    }
}

Вызовите этот файл: date.awk

Данные

2009-07-02 2009-07-07
1996-02-28 1996-03-03
2001-12-30 2002-01-04

Вызовите этот файл: date.txt

Результаты

Выполненная команда:

awk -f dates.awk dates.txt

Вывод:

2009-07-07
2009-07-06
2009-07-05
2009-07-04
2009-07-03
2009-07-02
1996-03-03
1996-03-02
1996-03-01
1996-02-29
1996-02-28
2002-01-04
2002-01-03
2002-01-02
2002-01-01
2001-12-31
2001-12-30
1 голос
/ 08 апреля 2017

Другой вариант - использовать dateseq из dateutils (http://www.fresse.org/dateutils/#dateseq). -i изменяет формат ввода, а -f изменяет формат вывода. -1 необходимо указывать в качестве приращения, если первая дата позже второй даты .

$ dateseq -i %m/%d/%Y -f %m/%d/%Y 7/7/2009 -1 7/2/2009
07/07/2009
07/06/2009
07/05/2009
07/04/2009
07/03/2009
07/02/2009
$ dateseq 2017-04-01 2017-04-05
2017-04-01
2017-04-02
2017-04-03
2017-04-04
2017-04-05
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...