Как конфертировать из дня в году и года в дату ГГГГММДД?

11

Я хочу перейти от дня года (1-366) и года (например, 2011) к дате в формате ГГГГММДД?

Умбер Ферруле
источник
1
Какой день представляет день 0? Обычно это 1-366.
Микель

Ответы:

17

Эта функция Bash работает для меня в системе на основе GNU:

jul () { date -d "$1-01-01 +$2 days -1 day" "+%Y%m%d"; }

Некоторые примеры:

$ y=2011; od=0; for d in {-4..4} 59 60 {364..366} 425 426; do (( d > od + 1)) && echo; printf "%3s " $d; jul $y $d; od=$d; done
 -4 20101227
 -3 20101228
 -2 20101229
 -1 20101230
  0 20101231
  1 20110101
  2 20110102
  3 20110103
  4 20110104

 59 20110228
 60 20110301

364 20111230
365 20111231
366 20120101

425 20120229
426 20120301

Эта функция считает нулевой юлианский день последним днем ​​предыдущего года.

А вот функция bash для систем на основе UNIX, таких как macOS:

jul () { (( $2 >=0 )) && local pre=+; date -v$pre$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
Приостановлено до дальнейшего уведомления.
источник
2
Это отличное решение. Только дата GNU, но намного короче.
Микель
Я никогда не знал, что дата GNU была такой гибкой. Фантастический.
Njd
Какое хорошее решение с использованием даты GNU. Если вы хотите сделать то же самое в системе UNIX, такой как macOS, вы можете использовать:jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
mcantsin
1
@mcantsin: спасибо за версию для MacOS!
Приостановлено до дальнейшего уведомления.
1
@mcantsin: я изменил вашу версию, чтобы она работала с отрицательными смещениями.
Приостановлено до дальнейшего уведомления.
4

Не может быть сделано только в Bash, но если у вас есть Perl:

use POSIX;

my ($jday, $year) = (100, 2011);

# Unix time in seconds since Jan 1st 1970
my $time = mktime(0,0,0, $jday, 0, $year-1900);

# same thing as a list that we can use for date/time formatting
my @tm = localtime $time;

my $yyyymmdd = strftime "%Y%m%d", @tm;
NJD
источник
1
Я на самом деле почти уверен, что его можно сделать только в Bash, хотя и не так элегантно (в худшем случае вычисление форматирования метки времени). Но я не перед машиной Linux, чтобы проверить это.
Бобби
Бобби, ты можешь подумать о dateкоманде в coreutils. Я проверил это, и он поддерживает только форматирование текущей даты или установку нового значения, поэтому это не вариант.
Банди
С помощью dateкоманды можно сделать. Смотри мой ответ. Но путь Perl намного проще и быстрее.
Микель
2
@bandi: Если толькоdate -d
user1686
+1: использовал это - спасибо, но выше появился метод date -d.
Umber Ferrule
4

Запустите, info 'Date input formats'чтобы увидеть, какие форматы разрешены.

Формат даты ГГГГ-ДДД , кажется, не существует, и пытается

$ date -d '2011-011'
date: invalid date `2011-011'

показывает, что это не работает, так что я думаю, что njdэто правильно, лучше всего использовать внешний инструмент, кроме bashи date.

Если вы действительно хотите использовать только инструменты командной строки и bash, вы можете сделать что-то вроде этого:

julian_date_to_yyyymmdd()
{
    date=$1    # assume all dates are in YYYYMMM format
    year=${date%???}
    jday=${date#$year}
    for m in `seq 1 12`; do
        for d in `seq 1 31`; do
            yyyymmdd=$(printf "%d%02d%02d" $year $m $d)
            j=$(date +"%j" -d "$yyyymmdd" 2>/dev/null)
            if test "$jday" = "$j"; then
                echo "$yyyymmdd"
                return 0
            fi
        done
    done
    echo "Invalid date" >&2
    return 1
}

Но это довольно медленный способ сделать это.

Более быстрый, но более сложный способ пытается циклически повторять каждый месяц, находит последний день в этом месяце и затем определяет, находится ли юлианский день в этом диапазоне.

# year_month_day_to_jday <year> <month> <day> => <jday>
# returns 0 if date is valid, non-zero otherwise
# year_month_day_to_jday 2011 2 1 => 32
# year_month_day_to_jday 2011 1 32 => error
year_month_day_to_jday()
{
    # XXX use local or typeset if your shell supports it
    _s=$(printf "%d%02d%02d" "$1" "$2" "$3")
    date +"%j" -d "$_s"
}

# last_day_of_month_jday <year> <month>
# last_day_of_month_jday 2011 2 => 59
last_day_of_month_jday()
{
    # XXX use local or typeset if you have it
    _year=$1
    _month=$2
    _day=31

    # GNU date exits with 0 if day is valid, non-0 if invalid
    # try counting down from 31 until we find the first valid date
    while test $_day -gt 0; do
        if _jday=$(year_month_day_to_jday $_year $_month $_day 2>/dev/null); then
            echo "$_jday"
            return 0
        fi
        _day=$((_day - 1))
    done
    echo "Invalid date" >&2
    return 1
}

# first_day_of_month_jday <year> <month>
# first_day_of_month_jday 2011 2 => 32
first_day_of_month_jday()
{
    # XXX use local or typeset if you have it
    _year=$1
    _month=$2
    _day=1

    if _jday=$(year_month_day_to_jday $_year $_month 1); then
        echo "$_jday"
        return 0
    else
        echo "Invalid date" >&2
        return 1
    fi
}

# julian_date_to_yyyymmdd <julian day> <4-digit year>
# e.g. julian_date_to_yyyymmdd 32 2011 => 20110201
julian_date_to_yyyymmdd()
{
    jday=$1
    year=$2

    for m in $(seq 1 12); do
        endjday=$(last_day_of_month_jday $year $m)
        if test $jday -le $endjday; then
            startjday=$(first_day_of_month_jday $year $m)
            d=$((jday - startjday + 1))
            printf "%d%02d%02d\n" $year $m $d
            return 0
        fi
    done
    echo "Invalid date" >&2
    return 1
}
Mikel
источник
last_day_of_month_jdayтакже может быть реализовано с использованием, например, date -d "$yyyymm01 -1 day"(только дата GNU) или $(($(date +"%s" -d "$yyyymm01") - 86400)).
Микель
Bash может сделать декремент вроде этого: ((day--)). У Bash есть forтакие циклы for ((m=1; m<=12; m++))(нет необходимости seq). Довольно безопасно предположить, что оболочки, которые имеют некоторые другие функции, которые вы используете, имеют local.
Приостановлено до дальнейшего уведомления.
POSIX не определяет локальное IIRC, но, безусловно, если при использовании ksh задан typeset, zsh имеет local, а zsh объявил IIRC. Я думаю, что набор текста работает во всех 3, но не работает в золе / тире. Я склонен недооценивать стиль ksh для. Спасибо за мысли.
Микель
@Mikel: Dash, localкак и BusyBox ash.
Приостановлено до дальнейшего уведомления.
Да, но не набрано IIRC. Все остальные имеют набор текста. Если бы у dash тоже был набор текста, я бы использовал это в примере.
Микель
1

Если день года (1-366) равен 149, а год - 2014 ,

$ date -d "148 days 2014-01-01" +"%Y%m%d"
20140529

Обязательно введите значение дня года -1 .

justreturn
источник
0

Мое решение в Bash

from_year=2013
from_day=362

to_year=2014
to_day=5

now=`date +"%Y/%m/%d" -d "$from_year/01/01 + $from_day days - 2 day"`
end=`date +"%Y/%m/%d" -d "$to_year/01/01 + $to_day days - 1 day"`


while [ "$now" != "$end" ] ; 
do
    now=`date +"%Y/%m/%d" -d "$now + 1 day"`;
    echo "$now";
    calc_day=`date -d "$now" +%G'.'%j`
    echo $calc_day
done
Себ
источник
0

На терминале POSIX:

jul () { date -v$1y -v1m -v1d -v+$2d -v-1d "+%Y%m%d"; }

Тогда звоните как

jul 2011 012
jul 2017 216
jul 2100 60
vpipkt
источник
0

Я понимаю, что об этом спрашивали много лет назад, но ни один из ответов, которые я вижу, не является чистым BASH, поскольку все они используют GNU date. Я думал, что попробую ответить ... Но я предупреждаю вас заранее, ответ не элегантный и не короткий, более 100 строк. Это можно было срезать, но я хотел, чтобы другие легко увидели, что он делает.

Основными «хитростями» здесь является выяснение, является ли год високосным (или нет), получая МОДУЛЬ %года, разделенного на 4, а затем просто складывая дни в каждом месяце, плюс дополнительный день в феврале, если необходимо. , используя простую таблицу значений.

Пожалуйста, не стесняйтесь комментировать и давать предложения о том, как сделать это лучше, так как я в первую очередь здесь, чтобы узнать больше сам, и я бы в лучшем случае считал себя новичком BASH. Я приложил все усилия, чтобы сделать это настолько портативным, насколько я знаю, и это означает некоторые компромиссы по моему мнению.

Что касается кода ... Я надеюсь, он довольно понятен.

#!/bin/sh

# Given a julian day of the year and a year as ddd yyyy, return
# the values converted to yyyymmdd format using ONLY bash:

ddd=$1
yyyy=$2
if [ "$ddd" = "" ] || [ "$yyyy" = "" ]
then
  echo ""
  echo "   Usage: <command> 123 2016"
  echo ""
  echo " A valid julian day from 1 to 366 is required as the FIRST"
  echo " parameter after the command, and a valid 4-digit year from"
  echo " 1901 to 2099 is required as the SECOND. The command and each"
  echo " of the parameters must be separated from each other with a space."
  echo " Please try again."
  exit
fi
leap_yr=$(( yyyy % 4 ))
if [ $leap_yr -ne 0 ]
then
  leap_yr=0
else
  leap_yr=1
fi
last_doy=$(( leap_yr + 365 ))
while [ "$valid" != "TRUE" ]
do
  if [ 0 -lt "$ddd" ] && [ "$last_doy" -ge "$ddd" ]
  then
    valid="TRUE"
  else
    echo "   $ddd is an invalid julian day for the year given."
    echo "   Please try again with a number from 1 to $last_doy."
    exit    
  fi
done
valid=
while [ "$valid" != "TRUE" ]
do
  if [ 1901 -le "$yyyy" ] && [ 2099 -ge "$yyyy" ]
  then
    valid="TRUE"
  else
    echo "   $yyyy is an invalid year for this script."
    echo "   Please try again with a number from 1901 to 2099."
    exit    
  fi
done
if [ "$leap_yr" -eq 1 ]
then
  jan=31  feb=60  mar=91  apr=121 may=152 jun=182
  jul=213 aug=244 sep=274 oct=305 nov=335
else
  jan=31  feb=59  mar=90  apr=120 may=151 jun=181
  jul=212 aug=243 sep=273 oct=304 nov=334
fi
if [ "$ddd" -gt $nov ]
then
  mm="12"
  dd=$(( ddd - nov ))
elif [ "$ddd" -gt $oct ]
then
  mm="11"
  dd=$(( ddd - oct ))
elif [ "$ddd" -gt $sep ]
then
  mm="10"
  dd=$(( ddd - sep ))
elif [ "$ddd" -gt $aug ]
then
  mm="09"
  dd=$(( ddd - aug ))
elif [ "$ddd" -gt $jul ]
then
  mm="08"
  dd=$(( ddd - jul ))
elif [ "$ddd" -gt $jun ]
then
  mm="07"
  dd=$(( ddd - jun ))
elif [ "$ddd" -gt $may ]
then
  mm="06"
  dd=$(( ddd - may ))
elif [ "$ddd" -gt $apr ]
then
  mm="05"
  dd=$(( ddd - apr ))
elif [ "$ddd" -gt $mar ]
then
  mm="04"
  dd=$(( ddd - mar ))
elif [ "$ddd" -gt $feb ]
then
  mm="03"
  dd=$(( ddd - feb ))
elif [ "$ddd" -gt $jan ]
then
  mm="02"
  dd=$(( ddd - jan ))
else
  mm="01"
  dd="$ddd"
fi
if [ ${#dd} -eq 1 ]
then
  dd="0$dd"
fi
if [ ${#yyyy} -lt 4 ]
then
  until [ ${#yyyy} -eq 4 ]
  do
    yyyy="0$yyyy"
  done
fi
printf '\n   %s%s%s\n\n' "$yyyy" "$mm" "$dd"
Дейв Лидик
источник
Кстати, расчет фактического високосного года немного сложнее, чем «делится на 4».
Эрих
@erich - Вы правы, но в течение лет, допустимых этим сценарием (1901-2099), ситуации, которые не работают, не возникнут. Не очень сложно добавить тест «или» для работы с годами, которые делятся поровну на 100, но не делятся поровну на 400, чтобы покрыть те случаи, если пользователь должен расширить это, но я не чувствовал, что это действительно нужно в этот случай. Возможно, это было близоруко от меня?
Дейв Лидик
0
OLD_JULIAN_VAR=$(date -u -d 1840-12-31 +%s)

TODAY_DATE=`date --date="$odate" +"%Y-%m-%d"`
TODAY_DATE_VAR=`date -u -d "$TODAY_DATE" +"%s"`
export JULIAN_DATE=$((((TODAY_DATE_VAR - OLD_JULIAN_VAR))/86400))
echo $JULIAN_DATE

математически представлено ниже

[(date in sec)-(1840-12-31 in sec)]/(sec in a day 86400)
user997434
источник