Take the 2-minute tour ×
Super User is a question and answer site for computer enthusiasts and power users. It's 100% free, no registration required.

I'm looking to go from day of year (1-366) and year (e.g. 2011) to a date in the format YYYYMMDD?

share|improve this question
1  
What day does day 0 represent? Normally it's 1-366. –  Mikel Jan 11 '11 at 13:41
add comment

5 Answers 5

up vote 9 down vote accepted

This Bash function works for me on a GNU-based system:

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

Some examples:

$ 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

This function considers Julian day zero to be the last day of the previous year.

share|improve this answer
1  
That's an awesome solution. GNU date only, but so much shorter. –  Mikel Jan 12 '11 at 4:13
    
I never knew GNU date was that flexible. Fantastic. –  njd Jan 12 '11 at 13:31
    
+1: Just the job - cheers. –  Umber Ferrule Jan 13 '11 at 14:21
add comment

Run info 'Date input formats' to see what formats are allowed.

The YYYY-DDD date format does not seem to be there, and trying

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

shows it doesn't work, so I think njd is correct, the best way is to use an external tool other than bash and date.

If you really want to use only bash and basic command line tools, you could do something like this:

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
}

But that's a pretty slow way to do it.

A faster but more complex way tries to loop over each month, finds the last day in that month, then sees if the Julian day is in that range.

# 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
}
share|improve this answer
    
last_day_of_month_jday could also be implemented using e.g. date -d "$yyyymm01 -1 day" (GNU date only) or $(($(date +"%s" -d "$yyyymm01") - 86400)). –  Mikel Jan 11 '11 at 13:43
    
+1: for effort to produce Bash solution. –  Umber Ferrule Jan 11 '11 at 16:16
    
Bash can do decrement like this: ((day--)). Bash has for loops like this for ((m=1; m<=12; m++)) (no need for seq). It's pretty safe to assume that shells that have some of the other features you're using have local. –  Dennis Williamson Jan 12 '11 at 3:55
    
IIRC local isn't specified by POSIX, but absolutely, if using ksh has typeset, zsh has local, and zsh has declare IIRC. I think typeset works in all 3, but doesn't work in ash/dash. I do tend to underuse ksh-style for. Thanks for the thoughts. –  Mikel Jan 12 '11 at 4:18
    
@Mikel: Dash has local as does BusyBox ash. –  Dennis Williamson Jan 12 '11 at 4:46
show 1 more comment

Can't be done in just Bash, but if you have 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;
share|improve this answer
1  
I'm actually pretty sure that his can be done in Bash only, though not that elegant (worst case calculating the timestamp formatting it). But I'm not in front of a Linux machine to test it out. –  Bobby Jan 11 '11 at 10:10
    
Bobby, you might think of the date command in the coreutils. I checked it and it only supports formatting the current date or setting it to a new value, so it is not an option. –  bandi Jan 11 '11 at 13:45
    
Using the date command can be done. See my answer. But the Perl way is much easier and faster. –  Mikel Jan 11 '11 at 13:49
2  
@bandi: Unless date -d –  grawity Jan 11 '11 at 14:24
    
+1: Was using this - thanks, but the date -d method above appeared. –  Umber Ferrule Jan 13 '11 at 14:21
show 2 more comments

If day of year (1-366) is 149 and year is 2014,

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

Be sure to input the day of year -1 value.

share|improve this answer
add comment

My solution 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
share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.