Essa é uma solução incrível. Somente data GNU, mas muito mais curta.
Mikel
Eu nunca soube que a data do GNU era tão flexível. Fantástico.
NJD
Que solução legal usando a data GNU. Caso deseje fazer o mesmo em um sistema UNIX, como o macOS, você pode usar:jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
mcantsin
1
@mcantsin: Obrigado pela versão do MacOS!
Pausado até novo aviso.
1
@mcantsin: eu modifiquei sua versão para que ela funcione com compensações negativas.
Pausado até novo aviso.
4
Não pode ser feito apenas no Bash, mas se você tiver o 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;
Na verdade, tenho certeza de que o dele pode ser feito apenas no Bash, embora não seja tão elegante (no pior caso, calculando o carimbo de data / hora que está sendo formatado). Mas não estou na frente de uma máquina Linux para testá-la.
Bobby
Bobby, você pode pensar no datecomando nos coreutils. Eu verifiquei e ele suporta apenas a formatação da data atual ou a configuração para um novo valor, portanto, não é uma opção.
bandi
O uso do datecomando pode ser feito. Veja minha resposta. Mas o caminho Perl é muito mais fácil e rápido.
Mikel
2
@bandi: Unlessdate -d
user1686
+1: estava usando isso - obrigado, mas o método date -d acima apareceu.
Umber Ferrule
4
Execute info 'Date input formats'para ver quais formatos são permitidos.
$ date -d '2011-011'
date: invalid date `2011-011'
mostra que não funciona, então acho que njdestá correto, a melhor maneira é usar uma ferramenta externa diferente de bashe date.
Se você realmente deseja usar apenas ferramentas básicas e de linha de comando básicas, pode fazer algo assim:
julian_date_to_yyyymmdd(){
date=$1 # assume all dates are in YYYYMMM format
year=${date%???}
jday=${date#$year}for m in`seq 1 12`;dofor 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"return0fidonedone
echo "Invalid date">&2return1}
Mas essa é uma maneira bem lenta de fazer isso.
Uma maneira mais rápida, porém mais complexa, tenta fazer um loop a cada mês, encontra o último dia desse mês e verifica se o dia juliano está nesse intervalo.
# 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 datewhile test $_day -gt 0;doif _jday=$(year_month_day_to_jday $_year $_month $_day 2>/dev/null);then
echo "$_jday"return0fi
_day=$((_day -1))done
echo "Invalid date">&2return1}# 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=1if _jday=$(year_month_day_to_jday $_year $_month 1);then
echo "$_jday"return0else
echo "Invalid date">&2return1fi}# 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=$2for m in $(seq 112);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 $dreturn0fidone
echo "Invalid date">&2return1}
last_day_of_month_jdaytambém pode ser implementado usando, por exemplo, date -d "$yyyymm01 -1 day"(somente data GNU) ou $(($(date +"%s" -d "$yyyymm01") - 86400)).
11111 Mikel
Bash pode fazer decréscimo assim: ((day--)). O Bash possui forloops como esse for ((m=1; m<=12; m++))(não é necessário seq). É bastante seguro supor que os shells que possuem alguns dos outros recursos que você está usando possuem local.
Pausado até novo aviso.
O local IIRC não é especificado pelo POSIX, mas com certeza, se o uso do ksh tiver sido formatado, o zsh tiver o local e o zsh declarar o IIRC. Acho typeset funciona em todos os 3, mas não funciona em ash / dash. Eu costumo subutilizar o estilo ksh para. Obrigado pelos pensamentos.
Mikel
@ Mikel: Dash tem localcomo o BusyBox ash.
Pausado até novo aviso.
Sim, mas não digitado IIRC. Todos os outros têm tipografia. Se o dash também tivesse sido digitado, eu teria usado isso no exemplo.
Mikel
1
Se o dia do ano (1-366) for 149 e o ano for 2014 ,
$ date -d "148 days 2014-01-01"+"%Y%m%d"20140529
Certifique-se de inserir o valor do dia -1 do ano .
Sei que isso foi perguntado há muito tempo, mas nenhuma das respostas que vejo são o que considero puro BASH, pois todas elas usam o GNU date. Eu pensei que iria tentar responder ... Mas aviso-o com antecedência, a resposta não é elegante, nem é curta em mais de 100 linhas. Poderia ser reduzido, mas eu queria deixar os outros verem facilmente o que faz.
Os principais "truques" aqui são descobrir se um ano é um ano bissexto (ou não) %, dividindo o MÓDULO do ano por 4 e depois somando os dias de cada mês, além de um dia extra para fevereiro, se necessário , usando uma tabela simples de valores.
Por favor, sinta-se à vontade para comentar e oferecer sugestões sobre maneiras de fazer isso melhor, já que estou aqui principalmente para aprender mais, e eu me consideraria um novato no BASH. Fiz o possível para tornar isso o mais portátil possível, o que significa alguns compromissos na minha opinião.
Para o código ... espero que seja bastante auto-explicativo.
#!/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=$2if["$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."
exitfi
leap_yr=$(( yyyy %4))if[ $leap_yr -ne 0]then
leap_yr=0else
leap_yr=1fi
last_doy=$(( leap_yr +365))while["$valid"!="TRUE"]doif[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 fidone
valid=while["$valid"!="TRUE"]doif[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 fidoneif["$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=335else
jan=31 feb=59 mar=90 apr=120 may=151 jun=181
jul=212 aug=243 sep=273 oct=304 nov=334fiif["$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"fiif[ ${#dd}-eq 1]then
dd="0$dd"fiif[ ${#yyyy}-lt 4]thenuntil[ ${#yyyy}-eq 4]do
yyyy="0$yyyy"donefi
printf '\n %s%s%s\n\n'"$yyyy""$mm""$dd"
fyi, o cálculo do ano bissexto real é um pouco mais complexo do que "é divisível por 4".
Erich
@erich - Você está correto, mas dentro do intervalo de anos permitido como válido por este script (1901-2099), situações que não funcionam não ocorrerão. Não é terrivelmente difícil adicionar um teste "ou" para lidar com anos que são igualmente divisíveis por 100, mas não igualmente divisíveis por 400, para cobrir esses casos, se o usuário precisar estender isso, mas não achei que isso fosse realmente necessário. este caso. Talvez isso tenha sido míope de mim?
Respostas:
Esta função Bash funciona para mim em um sistema baseado em GNU:
Alguns exemplos:
Essa função considera o dia juliano zero como o último dia do ano anterior.
E aqui está uma função bash para sistemas baseados em UNIX, como o macOS:
fonte
jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
Não pode ser feito apenas no Bash, mas se você tiver o Perl:
fonte
date
comando nos coreutils. Eu verifiquei e ele suporta apenas a formatação da data atual ou a configuração para um novo valor, portanto, não é uma opção.date
comando pode ser feito. Veja minha resposta. Mas o caminho Perl é muito mais fácil e rápido.date -d
Execute
info 'Date input formats'
para ver quais formatos são permitidos.O formato da data AAAA-DDD parece não estar presente e tentar
mostra que não funciona, então acho que
njd
está correto, a melhor maneira é usar uma ferramenta externa diferente debash
edate
.Se você realmente deseja usar apenas ferramentas básicas e de linha de comando básicas, pode fazer algo assim:
Mas essa é uma maneira bem lenta de fazer isso.
Uma maneira mais rápida, porém mais complexa, tenta fazer um loop a cada mês, encontra o último dia desse mês e verifica se o dia juliano está nesse intervalo.
fonte
last_day_of_month_jday
também pode ser implementado usando, por exemplo,date -d "$yyyymm01 -1 day"
(somente data GNU) ou$(($(date +"%s" -d "$yyyymm01") - 86400))
.((day--))
. O Bash possuifor
loops como essefor ((m=1; m<=12; m++))
(não é necessárioseq
). É bastante seguro supor que os shells que possuem alguns dos outros recursos que você está usando possuemlocal
.local
como o BusyBoxash
.Se o dia do ano (1-366) for 149 e o ano for 2014 ,
Certifique-se de inserir o valor do dia -1 do ano .
fonte
Minha solução no bash
fonte
Em um terminal POSIX:
Então ligue como
fonte
Sei que isso foi perguntado há muito tempo, mas nenhuma das respostas que vejo são o que considero puro BASH, pois todas elas usam o GNU
date
. Eu pensei que iria tentar responder ... Mas aviso-o com antecedência, a resposta não é elegante, nem é curta em mais de 100 linhas. Poderia ser reduzido, mas eu queria deixar os outros verem facilmente o que faz.Os principais "truques" aqui são descobrir se um ano é um ano bissexto (ou não)
%
, dividindo o MÓDULO do ano por 4 e depois somando os dias de cada mês, além de um dia extra para fevereiro, se necessário , usando uma tabela simples de valores.Por favor, sinta-se à vontade para comentar e oferecer sugestões sobre maneiras de fazer isso melhor, já que estou aqui principalmente para aprender mais, e eu me consideraria um novato no BASH. Fiz o possível para tornar isso o mais portátil possível, o que significa alguns compromissos na minha opinião.
Para o código ... espero que seja bastante auto-explicativo.
fonte
matematicamente representado abaixo
fonte