I am trying to parse out just the date from 2017-03-08T19:41:26Z.

The desired output is 2017-03-08.

  • 8
    Grab the first 11 characters? Is the timestamp in a variable? File?
    – Jeff Schaller
    Commented Mar 8, 2017 at 21:28

10 Answers 10


To extract the part before T, with POSIX shells:

utc_date=${time%T*} # as already said

Or to be Bourne compatible or for non-POSIX shells:

expr "$time" : '\(.*\)T'

Now, note that 2017-03-08T19:41:26Z is the zulu time (another name for UTC), an unambiguous specification of a precise instant in time.

At that time, the date was 2017-03-08 in London, but 2017-03-09 (in the early morning) in Bangkok.

If you wanted to know the local date (as opposed to the UTC date) for that time specification, that is for a Bangkok user to get 2017-03-09 and the London user to get 2017-03-08, there are a few options.

With GNU date:


date -d "$time" +%F

(easy as GNU date recognises that zulu format out of the box)

Same with ksh93:

printf '%(%F)T\n' "$time"

With zsh built-ins:

zmodload zsh/datetime
TZ=UTC0 strftime -rs unix_time %Y-%m-%dT%TZ $time &&
strftime %Y-%m-%d $unix_time

(you can replace %Y-%m-%d with %F on systems like GNU ones where strftime()/strptime() support it).

Similar with busybox date:

unix_time=$(date -u -D %Y-%m-%dT%TZ -d "$time" +%s)
date -d "@$unix_time" +%Y-%m-%d
  • busybox could convert in one step: busybox date +'%F' -u -D '%FT%T%Z' -d "$d". No need to get the seconds %s first. Using the '-u' option makes the output located in ZULU timezone, no need to use TZ.
    – user232326
    Commented Mar 9, 2017 at 3:50
  • @sorontar. Thanks for the ksh93 typo. Well spotted. Fixed now. For busybox, the point is to parse the date in the zulu timezone, but output it in the user's timezone, not zulu (so the Bangkok user gets 2017-03-09), so we can't use -u here for both. I still changed the TZ=UTC0 for the shorter -u on the parsing step. Commented Mar 9, 2017 at 8:05
  • @sorontar, not sure what you mean. TZ=Asia/Bangkok busybox date -D %FT%TZ%z -d "${date}+0000" +%F could in theory work if busybox was linked to the GNU libc whose strptime() accepts that %z as an extension. I did try it yesterday with Debian's busybox built statically and it didn't work. Tried again with a busybox dynamically linked to the glibc and it still doesn't work. Commented Mar 9, 2017 at 9:25
  • @sorontar, the glibc documentation has: Note: Currently, this is not fully implemented. The format is recognized, input is consumed but no field in TM is set Commented Mar 9, 2017 at 9:28
  • 1
    You are correct, busybox date has not implemented (yet) the parsing of a TimeZone value in the input string. It is clear now why you use two steps (if it required that the output is in the user locale)
    – user232326
    Commented Mar 9, 2017 at 21:20

Using cut:

cut - remove sections from each line of files

-d, --delimiter=DELIM use DELIM instead of TAB for field delimiter

-f, --fields=LIST select only these fields; also print any line that contains no delimiter character, unless the -s option is specified

echo "2017-03-08T19:41:26Z" | cut -d T -f 1

In Bash, you can use a number of operations in the parameter expansion of a variable:

date=${timestamp:0:10}          # pick 10 characters starting at position 0
date=${timestamp%T*}            # remove everything starting at the 'T'

(${variable%pattern} is actually part of the standard shell language, and supported also by simpler shells like dash.)

These are of course just simple substring operations. If you want to actually parse the date, you'll have to do something else. The substring expansion will of course fit nicely here since the format is fixed-width. But you may want to check that the values are valid etc.


Bash variable substitution can cover this.

echo $NEW

${string%substring} - Deletes shortest match of $substring from back of $string. More details at Manipulating Strings


So just find all the numbers-dash-numbers-dash-numbers? This would work with longer strings/lines, even without any "T"s immediately after the date.

grep -o "[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]"

Or equivalently, but slightly shorter using curly bracket "repeats/multiples"

grep -o '[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}'


$ echo 2017-03-08T19:41:26Z | grep -o '[0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}'

If there's not many other dashes in the input, you could search for any 4 characters, a dash, two characters, a dash, two more characters:

$ echo 2017-03-08T19:41:26Z | grep -o '....-..-..'


If you could use GNU date, this will do what you want:

$ d='2017-03-08T19:41:26Z'
$ date +'%F' -ud "$d"

Use the -u option to avoid some issues with the value of TZ (or locale).

If you need a date value in some other location, the value of the local TZ will work with this option:

$ date +'%F' -d "$d"
2017-03-08               # May change in some locales.

Or you can choose to use some specific value for TZ:

$ TZ=Asia/Kolkata date +'%F' -d "$d"
2017-03-09               # next day in India.

Note that Kolkata is the present name for the old Calcuta.


  1. could do it only if the value is an integer (in seconds):

    $ printf '%(%F)T\n' $(date +'%s' -d "$d")

    However bash is afected by the value of TZ (or locale):

    $ TZ=GGG+3 bash -c 'printf "%(%FT%T)T\n" $(date +"%s" -ud "$1")' sh "$d"

    Note the T16: above (not 19).

  2. could do both conversions (to seconds and the format):

    ksh$ printf '%(%F)T\n' "$d"

    But is equally affected by TZ (or locale):

    $ TZ=GGG-7 ksh93 -c 'printf "%(%FT%T)T\n" "$1"' sh "$d"

    Note the 09T02 (not 08T19)

  3. just needs a description of the format (-D) of the input (-d).

    $ busybox date -u -D '%Y-%m-%dT%TZ' -d "$d" +'%F'

    Using the -u avoids any effect of TZ on the output. However, the present version of busybox date has not implemented yet the parsing of a TimeZone value (-0400). But it may in the future, -z is a protection for the future.


If the value always has a T to indicate time, and it is always in Z (zulu time), this string operation will be enough (to get a zulu based date).

$ echo " ${d%%T*}"
  • timezone/TZ and locale/LANG/LC* are independent; you can change timezone without affecting locale, and you can change locale without changing timezone. Some date formats can be affected by locale (%c and %x at least) but %F should be yyyy-mm-dd (ISO8601) everywhere. Commented Mar 9, 2017 at 15:53
  • @dave_thompson_085 Yes, the locale may change several values. The value of TZ may be set by either (locale or/and environment TZ). It is, however, a single value. That value is what I meant, not other elements of the format.
    – user232326
    Commented Mar 9, 2017 at 21:05
  • You'd want busybox date -u -D '%Y-%m-%dT%TZ' -d "$d" +'%F'. busybox date uses the libc's (and busybox has been known to be linked with a wide variety of libcs) getopt()/getopt_long() to parse options, strptime() to parse dates, strftime() to format them. Not all getopt()s support options after arguments (and those that do stop doing it when POSIXLY_CORRECT is in the environment), not all strptime()s support %Z (and currently those that do ignore it) or %F. %F in strftime() is more widespread. It's specified though optional in POSIX. Commented Mar 10, 2017 at 9:39

Another way by parsing the string with awk:

echo 2017-03-08T19:41:26Z | awk -F"T" '{print $1}'

Or with sed:

echo "2017-03-08T19:41:26Z" | sed -e "s/T.*$//g"

You can grep up to the 'T'

echo 2017-03-08T19:41:26Z | grep -Eo "[^T]+" | head -1
  • 1
    In my system this outputs a second line with the time, not only the date as requested by OP. It would work if you take only the first line by piping head -1 for example. Commented Mar 9, 2017 at 8:25

I'd use perl parse the whole date string and format it to your desire:

#!/usr/#bin/env perl
use strict;
use warnings;

use Time::Piece;

while ( <DATA> ) { 
    print Time::Piece -> strptime ( $_, "%Y-%m-%dT%H:%M:%SZ")->strftime("%Y-%m-%d");


And as a one liner:

perl -MTime::Piece -lne 'print Time::Piece->strptime($_,"%Y-%m-%dT%H:%M:%SZ")->strftime("%Y-%m-%d")'

Crude, but purpose-specific: echo $in_date | sed 's/T.*//g'

For example:

echo $in_date | sed 's/T.*//g'

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .