82

How would you parse a date in bash, with separate fields (years, months, days, hours, minutes, seconds) into different variables?

The date format is: YYYY-MM-DD hh:mm:ss

Dave Jarvis
  • 30,436
  • 41
  • 178
  • 315
Steve
  • 829
  • 1
  • 6
  • 3

10 Answers10

98

Does it have to be bash? You can use the GNU coreutils /bin/date binary for many transformations:

 $ date --date="2009-01-02 03:04:05" "+%d %B of %Y at %H:%M and %S seconds"
 02 January of 2009 at 03:04 and 05 seconds

This parses the given date and displays it in the chosen format. You can adapt that at will to your needs.

Josip Rodin
  • 725
  • 6
  • 13
Dirk Eddelbuettel
  • 360,940
  • 56
  • 644
  • 725
  • 10
    Is this a Linux extension? Neither FreeBSD or Mac OSX seem to support it. – D.Shawley Dec 03 '09 at 20:57
  • 2
    It's plain old GNU date: $ date --version date (GNU coreutils) 7.4 Copyright (C) 2009 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Written by David MacKenzie. – Dirk Eddelbuettel Dec 03 '09 at 21:10
  • 2
    `date --date="\`date\`"` works for me and produces same output as date. Version is `date (GNU coreutils) 8.20`. Which `date` do you use ? – Stéphane Gourichon Nov 18 '13 at 15:52
  • 14
    `--date` / `-d` is a GNU extension. POSIX only specifies one option (`-u`) for `date`. You can use `date -jf '%F %T' '2009-01-02 03:04:05' '+%d %B of %Y at %H:%M and %S seconds'` with BSD / OS X date. – Lri Mar 26 '14 at 15:05
  • @user495470 What would the format be if the time was 12 hour format? – rottweiler Oct 19 '17 at 00:12
51

I had a different input time format, so here is a more flexible solution.

Convert dates in BSD/macOS

date -jf in_format [+out_format] in_date

where the formats use strftime (see man strftime).

For the given input format YYYY-MM-DD hh:mm:ss:

$ date -jf '%Y-%m-%d %H:%M:%S' '2017-05-10 13:40:01'
Wed May 10 13:40:01 PDT 2017

To read them into separate variables, I'm taking NVRAM's idea, but allowing you to use any strftime format:

$ date_in='2017-05-10 13:40:01'

$ format='%Y-%m-%d %H:%M:%S'

$ read -r y m d H M S <<< "$(date -jf "$format" '+%Y %m %d %H %M %S' "$date_in")"

$ for var in y m d H M S; do echo "$var=${!var}"; done
y=2017
m=05
d=10
H=13
M=40
S=01

In scripts, always use read -r.

In my case, I wanted to convert between timezones (see your /usr/share/zoneinfo directory for zone names):

$ format=%Y-%m-%dT%H:%M:%S%z

$ TZ=UTC date -jf $format +$format 2017-05-10T02:40:01+0200
2017-05-10T00:40:01+0000

$ TZ=America/Los_Angeles date -jf $format +$format 2017-05-10T02:40:01+0200
2017-05-09T17:40:01-0700

Convert dates in GNU/Linux

On a Mac, you can install the GNU version of date as gdate with brew install coreutils.

date [+out_format] -d in_date

where the out_format uses strftime (see man strftime).

In GNU coreutils' date command, there is no way to explicitly set an input format, since it tries to figure out the input format by itself, and stuff usually just works. (For detail, you can read the manual at coreutils: Date input formats.)

For example:

$ date '+%Y %m %d %H %M %S' -d '2017-05-10 13:40:01'
2017 05 10 13 40 01

To read them into separate variables:

$ read -r y m d H M S <<< "$(date '+%Y %m %d %H %M %S' -d "$date_in")"

To convert between timezones (see your /usr/share/zoneinfo directory for zone names), you can specify TZ="America/Los_Angeles" right in your input string. Note the literal " chars around the zone name, and the space character before in_date:

TZ=out_tz date [+out_format] 'TZ="in_tz" in_date'

For example:

$ format='%Y-%m-%d %H:%M:%S%z'

$ TZ=America/Los_Angeles date +"$format" -d 'TZ="UTC" 2017-05-10 02:40:01'
2017-05-09 19:40:01-0700

$ TZ=UTC date +"$format" -d 'TZ="America/Los_Angeles" 2017-05-09 19:40:01'
2017-05-10 02:40:01+0000

GNU date also understands hour offsets for the time zone:

$ TZ=UTC date +"$format" -d '2017-05-09 19:40:01-0700'
2017-05-10 02:40:01+0000
Chaim Leib Halbert
  • 2,194
  • 20
  • 23
38

This is simple, just convert your dashes and colons to a space (no need to change IFS) and use 'read' all on one line:

read Y M D h m s <<< ${date//[-:]/ }

For example:

$ date=$(date +'%Y-%m-%d %H:%M:%S')
$ read Y M D h m s <<< ${date//[-: ]/ }
$ echo "Y=$Y, m=$m"
Y=2009, m=57
NVRAM
  • 6,947
  • 10
  • 41
  • 44
  • On Ubuntu 16.04.5 LTS, this gives me `Syntax error: redirection unexpected` – hamaney Sep 22 '18 at 01:11
  • Are you sure you're running bash? If so, what version? Check $SHELL (or `ps -fp $$`) if interactive or the shebang if it's a script. It works fine on bash 4.3.48 (Ubu 16.04.5 LTS). – NVRAM Sep 22 '18 at 06:41
7
$ t='2009-12-03 12:38:15'
$ a=(`echo $t | sed -e 's/[:-]/ /g'`)
$ echo ${a[*]}
2009 12 03 12 38 15
$ echo ${a[3]}
12
DigitalRoss
  • 143,651
  • 25
  • 248
  • 329
6

The array method is perhaps better, but this is what you were specifically asking for:

IFS=" :-"
read year month day hour minute second < <(echo "YYYY-MM-DD hh:mm:ss")
Steve Baker
  • 4,323
  • 1
  • 20
  • 15
  • 1
    I like this best. One shortcut, which also doesn't permanently change IFS: `IFS=" :-" read Y M D h m s <<<"YYYY-MM-DD hh:mm:ss"` – ephemient Dec 06 '09 at 03:45
3

Pure Bash:

date="2009-12-03 15:35:11"
saveIFS="$IFS"
IFS="- :"
date=($date)
IFS="$saveIFS"
for field in "${date[@]}"
do
    echo $field
done

2009
12
03
15
35
11
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • No need to use arrays here. Just `set junk $date; shift` instead of `date=($date)` and `for field` instead of the array loop. That would be a `pure sh` version, which is even better! – Idelic Dec 04 '09 at 08:08
2

instead of using the shell scripting,incorporate in your scripting itself like below wheever you need:

a=date +%Y 
b=date +%S
c=date +%H

a will be year b will be seconds c will be hours. and so on.

Vijay
  • 65,327
  • 90
  • 227
  • 319
2

Another solution to the OP's problem:

IFS=' -:' read y m d h m s<<<'2014-03-26 16:36:41'

Converting a date to another format with BSD date and GNU date:

$ LC_ALL=C date -jf '%a %b %e %H:%M:%S %Z %Y' 'Wed Mar 26 16:36:41 EET 2014' +%F\ %T
2014-03-26 16:36:41
$ gdate -d 'Wed Mar 26 16:36:41 EET 2014' +%F\ %T
2014-03-26 16:36:41

GNU date recognizes Wed and Mar even in non-English locales but BSD date doesn't.

Converting seconds since epoch to a date and time with GNU date and BSD date:

$ gdate -d @1234567890 '+%F %T'
2009-02-14 01:31:30
$ date -r 1234567890 '+%F %T'
2009-02-14 01:31:30

Converting seconds to hours, minutes, and seconds with a POSIX shell, POSIX awk, GNU date, and BSD date:

$ s=12345;printf '%02d:%02d:%02d\n' $((s/3600)) $((s%3600/60)) $((s%60))
05:25:45
$ echo 12345|awk '{printf "%02d:%02d:%02d\n",$0/3600,$0%3600/60,$0%60}'
05:25:45
$ gdate -d @12345 +%T
05:25:45
$ date -r 12345 +%T
05:25:45

Converting seconds to days, hours, minutes, and seconds:

$ t=12345678
$ printf '%d:%02d:%02d:%02d\n' $((t/86400)) $((t/3600%24)) $((t/60%60)) $((t%60))
142:21:21:18
Lri
  • 26,768
  • 8
  • 84
  • 82
1

another pure bash

$ d="2009-12-03 15:35:11"
$ d=${d//[- :]/|}
$ IFS="|"
$ set -- $d
$ echo $1
2009
$ echo $2
12
$ echo $@
2009 12 03 15 35 11
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
0

have you tried using cut? something like this: dayofweek=date|cut -d" " -f1

Jay
  • 13,803
  • 4
  • 42
  • 69