Ich möchte vom Tag des Jahres (1-366) und vom Jahr (z. B. 2011) zu einem Datum im Format JJJJMMTT wechseln.
Ich möchte vom Tag des Jahres (1-366) und vom Jahr (z. B. 2011) zu einem Datum im Format JJJJMMTT wechseln.
Antworten:
Diese Bash-Funktion funktioniert für mich auf einem GNU-basierten System:
jul () { date -d "$1-01-01 +$2 days -1 day" "+%Y%m%d"; }
Einige Beispiele:
$ 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
Diese Funktion betrachtet den Julianischen Tag Null als den letzten Tag des Vorjahres.
Und hier ist eine Bash-Funktion für UNIX-basierte Systeme wie macOS:
jul () { (( $2 >=0 )) && local pre=+; date -v$pre$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
Kann nicht nur in Bash gemacht werden, aber wenn Sie Perl haben:
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;
date
Befehl in den Coreutils. Ich habe es überprüft und es unterstützt nur das Formatieren des aktuellen Datums oder das Setzen auf einen neuen Wert, daher ist dies keine Option.
date
Befehl kann durchgeführt werden. Siehe meine Antwort. Aber der Perl-Weg ist viel einfacher und schneller.
date -d
Führen Sie aus, um info 'Date input formats'
zu sehen, welche Formate zulässig sind.
Das JJJJ-TTT-Datumsformat scheint nicht vorhanden zu sein und es zu versuchen
$ date -d '2011-011'
date: invalid date `2011-011'
zeigt, dass es nicht funktioniert, daher halte ich es für njd
richtig. Der beste Weg ist, ein anderes externes Tool als bash
und zu verwenden date
.
Wenn Sie wirklich nur Bash- und grundlegende Befehlszeilentools verwenden möchten, können Sie Folgendes tun:
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
}
Aber das ist ein ziemlich langsamer Weg.
Ein schnellerer, aber komplexerer Weg versucht, jeden Monat eine Schleife zu durchlaufen, findet den letzten Tag in diesem Monat und prüft dann, ob der julianische Tag in diesem Bereich liegt.
# 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
}
last_day_of_month_jday
könnte auch mit zB date -d "$yyyymm01 -1 day"
(nur GNU-Datum) oder implementiert werden $(($(date +"%s" -d "$yyyymm01") - 86400))
.
((day--))
. Bash hat solche for
Schleifen for ((m=1; m<=12; m++))
(keine Notwendigkeit seq
). Es ist ziemlich sicher anzunehmen, dass Shells, die einige der anderen Funktionen haben, die Sie verwenden local
.
local
wie BusyBox ash
.
Wenn der Tag des Jahres (1-366) 149 und das Jahr 2014 ist ,
$ date -d "148 days 2014-01-01" +"%Y%m%d"
20140529
Stellen Sie sicher, dass Sie den Wert für Tag des Jahres -1 eingeben.
Meine Lösung in 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
Mir ist klar, dass dies vor Ewigkeiten gefragt wurde, aber keine der Antworten, die ich sehe, ist das, was ich als reines BASH betrachte, da sie alle GNU verwenden date
. Ich dachte, ich würde versuchen zu antworten ... Aber ich warne Sie im Voraus, die Antwort ist weder elegant noch kurz mit über 100 Zeilen. Es könnte reduziert werden, aber ich wollte anderen leicht zeigen, was es tut.
Die wichtigsten "Tricks" hier sind herauszufinden, ob ein Jahr ein Schaltjahr ist (oder nicht), indem der MODUL %
des Jahres durch 4 geteilt wird und dann nur die Tage in jedem Monat addiert werden, plus ein zusätzlicher Tag für Februar, falls erforderlich unter Verwendung einer einfachen Wertetabelle.
Bitte zögern Sie nicht, Kommentare abzugeben und Vorschläge zu machen, wie Sie dies besser machen können, da ich in erster Linie hier bin, um selbst mehr zu lernen, und ich würde mich bestenfalls als BASH-Neuling betrachten. Ich habe mein Bestes getan, um dies so portabel wie möglich zu machen, und das bedeutet meiner Meinung nach einige Kompromisse.
Weiter zum Code ... Ich hoffe, er ist ziemlich selbsterklärend.
#!/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"
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
mathematisch dargestellt unten
[(date in sec)-(1840-12-31 in sec)]/(sec in a day 86400)