¿Cómo convertir el día del año y el año a una fecha AAAAMMDD?

11

Estoy buscando pasar del día del año (1-366) y el año (por ejemplo, 2011) a una fecha en el formato AAAAMMDD?

Virola Umber
fuente
1
¿Qué día representa el día 0? Normalmente es 1-366.
Mikel

Respuestas:

17

Esta función Bash funciona para mí en un sistema basado en GNU:

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

Algunos ejemplos:

$ 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

Esta función considera que el día juliano cero es el último día del año anterior.

Y aquí hay una función bash para sistemas basados ​​en UNIX, como macOS:

jul () { (( $2 >=0 )) && local pre=+; date -v$pre$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
Pausado hasta nuevo aviso.
fuente
2
Esa es una solución increíble. Fecha de GNU solamente, pero mucho más corta.
Mikel
Nunca supe que la fecha de GNU fuera tan flexible. Fantástico.
njd
Qué buena solución usando la fecha GNU. En caso de que quiera hacer lo mismo en un sistema UNIX, como macOS, puede usar:jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
mcants en
1
@mcantsin: ¡Gracias por la versión de MacOS!
Pausado hasta nuevo aviso.
1
@mcantsin: modifiqué tu versión para que funcione con compensaciones negativas.
Pausado hasta nuevo aviso.
4

No se puede hacer solo en Bash, pero si tienes 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
fuente
1
De hecho, estoy bastante seguro de que solo se puede hacer en Bash, aunque no es tan elegante (el peor de los casos calculando la marca de tiempo que lo formatea). Pero no estoy frente a una máquina Linux para probarlo.
Bobby
Bobby, podrías pensar en el datecomando en los coreutils. Lo revisé y solo admite formatear la fecha actual o configurarlo en un nuevo valor, por lo que no es una opción.
Bandi
Se puede usar el datecomando. Mira mi respuesta. Pero el camino de Perl es mucho más fácil y rápido.
Mikel
2
@bandi: A menosdate -d
user1686
+1: Estaba usando esto, gracias, pero apareció el método date -d anterior.
Umber Ferrule
4

Ejecute info 'Date input formats'para ver qué formatos están permitidos.

El formato de fecha AAAA-DDD no parece estar allí, e intentando

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

muestra que no funciona, así que creo que njdes correcto, la mejor manera es usar una herramienta externa que no sea bashy date.

Si realmente desea utilizar solo bash y herramientas básicas de línea de comandos, puede hacer algo como esto:

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
}

Pero esa es una forma bastante lenta de hacerlo.

Una forma más rápida pero más compleja intenta recorrer cada mes, encuentra el último día de ese mes y luego ve si el día juliano está en ese rango.

# 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
fuente
last_day_of_month_jdaytambién podría implementarse usando, por ejemplo, date -d "$yyyymm01 -1 day"(fecha GNU solamente) o $(($(date +"%s" -d "$yyyymm01") - 86400)).
Mikel
Bash puede hacer decremento de la siguiente manera: ((day--)). Bash tiene forbucles como este for ((m=1; m<=12; m++))(no es necesario seq). Es bastante seguro asumir que los shells que tienen algunas de las otras características que está usando tienen local.
Pausado hasta nuevo aviso.
IIIX local no está especificado por POSIX, pero absolutamente, si el uso de ksh tiene tipografía, zsh tiene local y zsh ha declarado IIRC. Creo que la composición tipográfica funciona en los 3, pero no funciona en ash / dash. Tiendo a subutilizar el estilo ksh para. Gracias por los pensamientos
Mikel
@Mikel: Dash tiene al localigual que BusyBox ash.
Pausado hasta nuevo aviso.
Sí, pero no tipografía IIRC. Todos los demás tienen tipografía. Si el guión también tuviera una composición tipográfica, lo habría usado en el ejemplo.
Mikel
1

Si el día del año (1-366) es 149 y el año es 2014 ,

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

Asegúrese de ingresar el valor del día del año -1 .

vuelta
fuente
0

Mi solución en 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
Seb
fuente
0

En un terminal POSIX:

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

Entonces llama como

jul 2011 012
jul 2017 216
jul 2100 60
vpipkt
fuente
0

Me doy cuenta de que esto se hizo hace mucho tiempo, pero ninguna de las respuestas que veo es lo que considero puro BASH ya que todos usan GNU date. Pensé en intentar responder ... Pero te advierto con anticipación, la respuesta no es elegante, ni corta en más de 100 líneas. Podría reducirse, pero quería dejar que otros vieran fácilmente lo que hace.

Los "trucos" principales aquí son descubrir si un año es bisiesto (o no) al obtener el MÓDULO %del año dividido por 4, y luego simplemente sumando los días de cada mes, más un día adicional para febrero si es necesario , utilizando una tabla de valores simple.

Por favor, siéntase libre de comentar y ofrecer sugerencias sobre maneras de hacerlo mejor, ya que principalmente estoy aquí para aprender más sobre mí mismo, y en el mejor de los casos me consideraría un novato de BASH. Hice todo lo posible para que esto sea lo más portátil posible, y eso significa algunos compromisos en mi opinión.

En el código ... Espero que se explique por sí mismo.

#!/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"
Dave Lydick
fuente
para su información, el cálculo real del año bisiesto es un poco más complejo que "es divisible por 4".
Erich
@erich: tiene razón, pero dentro del rango de años permitido como válido por este script (1901-2099), no ocurrirán situaciones que no funcionen. No es terriblemente difícil agregar una prueba "o" para lidiar con años que son divisibles por 100 pero no por 400 para cubrir esos casos si el usuario necesita extender esto, pero no sentí que realmente fuera necesario en este caso. Tal vez eso fue miope de mí?
Dave Lydick
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

representado matemáticamente a continuación

[(date in sec)-(1840-12-31 in sec)]/(sec in a day 86400)
usuario997434
fuente