5

Scenario:

I am fetching a date value from a file into a variable and it is in DD-MM-YYYY format by default. I have to substract this date from system date. Subtraction is giving incorrect result if I had both dates in DD-MM-YYYY format. So I read a bit on google and decided to format both dates as YYYY-MM-DD as this will give correct value after subtraction. I have System date formatted successfully in YYYY-MM-DD, but facing a hard time to convert the date obtained from the file to YYYY-MM-DD format.

Below solution works fine with single digit dates:

$ date -d $(sed "s/-/\//g" <<< '9-2-1832') +%Y-%m-%d
Output : 1832-09-02

but when I try to convert date in double digits like below:

$ date -d $(sed "s/-/\//g" <<< '19-07-2021') +%Y-%m-%d

I get output

Invalid date '19/07/2021'

Where:

19 - is Date of a month
07 - is Month i.e. July in this case.
2021 - is Year

Desired output as -> 2021-07-19

I am working on RH Linux with Date version as: date (GNU coreutils) 8.22

Please help to provide a solution for above problem.

9
  • 3
    The first one didn't work fine as it gave you 1832-09-02 instead of 1832-02-09 for 9-2-1832. IOW, your date interpreted the date as if it was in US format (m-d-y) instead of d-m-y). Jul 21, 2021 at 11:31
  • @StéphaneChazelas I can reproduce this on my Arch, and using LC_ALL=el_GR.utf8 or LC_ALL=es_ES.utf8 or LC_ALL=fr_FR.utf8 doesn't make a difference, although all of those use DD-MM-YYYY and print the right format with +%x. This is a bug in GNU date, right?
    – terdon
    Jul 21, 2021 at 11:59
  • 2
    @terdon, no, it works as documented, GNU date expects date is a limited number of formats, generally US style, and that's independant of the locale. See info date cal Jul 21, 2021 at 12:18
  • I find GNU date weird: it jumps through hoops to try to accept all sorts of "intuitive" date inputs, but doesn't let you tell it what the format is when it's impossible to guess. (BSD date is much nicer in this respect.) Granged, 19 can't be a month, but GNU date doesn't seem to go so far as to assume it must be a day and adjust accordingly.
    – chepner
    Jul 21, 2021 at 19:30
  • @chepner, about that, see that discussion on the coreutils mailing list and continued there Jul 22, 2021 at 6:30

7 Answers 7

11

You could switch to busybox date which allows specifying the input format:

$ date=19-7-2021
$ busybox date -D %d-%m-%Y -d "$date" +%F
2021-07-19

(beware it won't accept 019-007-2021 dates for instance).

Same with the ast-open implementation of date (unlikely to be available out of the box on your RedHat system):

$ date -p %d-%m-%Y -d "$date" +%F
2021-07-19

If you were on BSD instead of a GNU system, you'd do:

$ date -jf %d-%m-%Y -- "$date" +%F
2021-07-19

Or you could switch to zsh instead of bash as is has a date parsing and formatting builtin:

$ zmodload zsh/datetime
$ strftime -rs t %d-%m-%Y $date && strftime %F $t
2021-07-19

(strftime being the builtin that gives access to the standard strftime() and strptime() (with -r) APIs).

It also has proper splitting operators and a printf that can take arguments in arbitrary orders (using the %n$... syntax as found in most printf() implementations including GNU printf(), but not the printf builtin of bash nor the GNU printf standalone utility), and it doesn't treat numbers with leading zeros as octal by default:

$ printf '%3$04d-%2$02d-%1$01d\n' ${(s[-])date}
2021-07-19

Or using an anonymous function:

$ (){ printf '%04d-%02d-%02d\n' $3 $2 $1; } ${(s[-])date}
2021-07-19

Or here, the array reversing operator:

$ printf '%04d-%02d-%02d\n' ${(s[-]Oa)date}
2021-07-19
10

In your case, awk might be a better method:

$ awk -F'-' '{printf("%04d-%02d-%02d\n",$3,$2,$1)}' <<< '19-07-2021'
2021-07-19
4
  • Thanks @Stephane. I have posted my answer which helped solving my problem, but I think yours would work too as the approach looks similar.
    – MayD
    Jul 22, 2021 at 5:29
  • Awk is able to do it directly:awk -F'-' '{print strftime("%Y-%m-%d",mktime($3" "$2" "$1" 0 0 0"))}' <<< '19-07-2021'
    – done
    Jul 23, 2021 at 3:59
  • @ImHere True, but it is somewhat longer the simple printf approach, don't you think? ;)
    – AdminBee
    Jul 23, 2021 at 7:09
  • The simple printf doesn't validate the date.
    – done
    Jul 23, 2021 at 10:25
6

If the format is hardcoded as DD-YY-YYYY, you can also hardcode the whole expression and use substring extraction to reformat, which is very fast:

#!/bin/bash
dmy=$(date +%d-%m-%Y)
ymd="${dmy: -4}-${dmy:3:2}-${dmy:0:2}"
echo "$dmy -> $ymd"

If the format is D[D]-M[M]-YYYY you could use regular expressions to reformat, which is somewhat slower:

#!/bin/bash
dmy="9-2-1932"
if [[ "$dmy" =~ ^([[:digit:]]+)-([[:digit:]]+)-([[:digit:]]+)$ ]] ; then
  ymd="${BASH_REMATCH[3]}-${BASH_REMATCH[2]}-${BASH_REMATCH[1]}"
fi
echo "$dmy -> $ymd"

You can format that a bit nicer with printf:

dmy="9-02-2032"
if [[ "$dmy" =~ ^([[:digit:]]+)-([[:digit:]]+)-([[:digit:]]+)$ ]] ; then
  printf -v ymd '%.4d-%.2d-%.2d' \
    "${BASH_REMATCH[3]}" "${BASH_REMATCH[2]}" "${BASH_REMATCH[1]}"
fi
echo "$dmy -> $ymd"

A former version of this answer is evidence that I didn't read it fully, converting YYYY-MM-DD to DD-MM-YYYY:

#!/bin/bash

ymd=$(date  +%Y-%m-%d)
dmy="${ymd: -2}-${ymd:5:2}-${ymd:0:4}"
echo "$ymd -> $dmy"
3
  • ${ymd: -2}${ymd:4:4}${ymd:0:4}
    – nezabudka
    Jul 22, 2021 at 6:49
  • 1
    Note that one of their examples is the date string 9-2-1832.
    – Kusalananda
    Jul 22, 2021 at 17:09
  • @Kusalananda That was a it hidden, but I can't read anyway. Thanks for the note. Jul 23, 2021 at 17:58
4

Thanks all you guys for pitching in with various solutions. I believe few of the suggested solutions would work out for me as I correlate them to the approach I used in solving it on my own yesterday, right after posting this question.

I used IFS to achieve it.

IFS=- read d m y <<<'19-7-2021'
printf '%.4d-%.2d-%.2d\n' "${y#0}" "${m#0}" "${d#0}"

This would output 2021-07-19.

The ${m#0} etc. removes an initial 0 from the value of $m, in case the original month happens to be 08 or 09 which would otherwise be interpreted as invalid octal numbers.

1
  • Just FYI you can force bash computations to be in base 10 by using $((10#$m)). It usually helps also because it will not replace single-digit 0 with nothing (as does ${m#0}). Also, even empty variables become 0. But neither can happen in your case, so this suggestion only make the whole slightly more verbose.
    – MoonCactus
    Jul 22, 2021 at 16:59
1
$ perl -lne 'print reverse split /(-)/, s/\b\d\b/0$&/rg' <<<"19-7-2021"
2021-07-19

Split on dash /(-)/ , with the dashes included in the split up array but before that change the date to double digits in day and/or month.

1

A quick and lean solution that uses only sed:

echo 19-07-2021 | sed -E 's/^([0-9]{1,2})-([0-9]{1,2})-([0-9]{4})$/\3-\2-\1/'
2
  • 5
    They also have an example saying 9-2-1832...
    – Kusalananda
    Jul 22, 2021 at 17:07
  • 1
    @Kusalananda Thanks, fixed. Aug 26, 2021 at 15:33
0

I had the same problem in many .txt files and wrote a simple one line:

sed -i -E 's/([0-9]{2})-([0-9]{2})-([0-9]{4})/\3-\2-\1/g' ./*.txt
2
  • 1
    This won't catch all the cases. See Kusalananda's comment on my answer. Aug 26, 2021 at 15:35
  • @DanielWerner so the dates are written badly, can't do much about it. Seems like your solution is the right one here. My is more general for proper data.
    – pbies
    Aug 26, 2021 at 17:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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