Wie konvertiere ich von Tag und Jahr zu einem Datum JJJJMMTT?

11

Ich möchte vom Tag des Jahres (1-366) und vom Jahr (z. B. 2011) zu einem Datum im Format JJJJMMTT wechseln.

Umber Ferrule
quelle
1
Welchen Tag repräsentiert Tag 0? Normalerweise ist es 1-366.
Mikel

Antworten:

17

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; }
Bis auf weiteres angehalten.
quelle
2
Das ist eine großartige Lösung. Nur GNU-Datum, aber viel kürzer.
Mikel
Ich wusste nie, dass das GNU-Datum so flexibel ist. Fantastisch.
NJD
Was für eine schöne Lösung mit GNU-Datum. jul () { date -v+$2d -v-1d -j -f "%Y-%m-%d" $1-01-01 +%Y%m%d; }
Wenn
1
@mcantsin: Danke für die MacOS-Version!
Bis auf weiteres angehalten.
1
@mcantsin: Ich habe deine Version so geändert, dass sie mit negativen Offsets funktioniert.
Bis auf weiteres angehalten.
4

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;
njd
quelle
1
Ich bin mir eigentlich ziemlich sicher, dass dies nur in Bash möglich ist, wenn auch nicht so elegant (im schlimmsten Fall wird der Zeitstempel berechnet, der ihn formatiert). Aber ich bin nicht vor einem Linux-Computer, um es zu testen.
Bobby
Bobby, du denkst vielleicht an den dateBefehl 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.
Bandi
Mit dem dateBefehl kann durchgeführt werden. Siehe meine Antwort. Aber der Perl-Weg ist viel einfacher und schneller.
Mikel
2
@ Bandi: Es sei denndate -d
user1686
+1: Ich habe dies verwendet - danke, aber die obige Methode date -d wurde angezeigt.
Umber Ferrule
4

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 njdrichtig. Der beste Weg ist, ein anderes externes Tool als bashund 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
}
Mikel
quelle
last_day_of_month_jdaykönnte auch mit zB date -d "$yyyymm01 -1 day"(nur GNU-Datum) oder implementiert werden $(($(date +"%s" -d "$yyyymm01") - 86400)).
Mikel
Bash kann so dekrementieren : ((day--)). Bash hat solche forSchleifen 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.
Bis auf weiteres angehalten.
IIRC local wird von POSIX nicht angegeben, aber wenn ksh einen Satz hat, hat zsh local und zsh deklariert IIRC. Ich denke, dass der Satz in allen 3 funktioniert, aber nicht in Asche / Strich. Ich neige dazu, ksh-Stil für zu wenig zu verwenden. Danke für die Gedanken.
Mikel
@Mikel: Dash hat localwie BusyBox ash.
Bis auf weiteres angehalten.
Ja, aber kein IIRC-Satz. Alle anderen haben einen Satz. Wenn dash auch gesetzt hätte, hätte ich das im Beispiel verwendet.
Mikel
1

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.

nur zurückkehren
quelle
0

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
Seb
quelle
0

Auf einem POSIX-Terminal:

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

Dann ruf gerne an

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

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"
Dave Lydick
quelle
Zu Ihrer Information, die tatsächliche Berechnung des Schaltjahres ist etwas komplexer als "ist durch 4 teilbar".
Erich
@erich - Sie haben Recht, aber innerhalb des von diesem Skript (1901-2099) gültigen Bereichs von Jahren treten Situationen, die nicht funktionieren, nicht auf. Es ist nicht besonders schwierig, einen "oder" -Test hinzuzufügen, um mit Jahren umzugehen, die gleichmäßig durch 100 teilbar sind, aber nicht gleichmäßig durch 400 teilbar sind, um diese Fälle abzudecken, wenn der Benutzer dies erweitern muss, aber ich hatte nicht das Gefühl, dass dies wirklich notwendig war dieser Fall. Vielleicht war das kurzsichtig von mir?
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

mathematisch dargestellt unten

[(date in sec)-(1840-12-31 in sec)]/(sec in a day 86400)
user997434
quelle