5

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 Mar 8 '17 at 21:28
28

To extract the part before T, with POSIX shells:

time=2017-03-08T19:41:26Z
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:

time=2017-03-08T19:41:26Z

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. – sorontar Mar 9 '17 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. – Stéphane Chazelas Mar 9 '17 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. – Stéphane Chazelas Mar 9 '17 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 – Stéphane Chazelas Mar 9 '17 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) – sorontar Mar 9 '17 at 21:20
12

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
10

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

timestamp='2017-03-08T19:41:26Z'
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.

6

Bash variable substitution can cover this.

OLD="2017-03-08T19:41:26Z"
NEW=${OLD%T*}
echo $NEW
2017-03-08

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

5

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\}'

Ex:

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

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 '....-..-..'
2017-03-08
4

date

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

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

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.

shell

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

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

    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"
    2017-03-08T16:41:26Z
    

    Note the T16: above (not 19).

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

    ksh$ printf '%(%F)T\n' "$d"
    2017-03-08
    

    But is equally affected by TZ (or locale):

    $ TZ=GGG-7 ksh93 -c 'printf "%(%FT%T)T\n" "$1"' sh "$d"
    2017-03-09T02:41:26
    

    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'
    2017-03-08
    

    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.

string

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*}"
2017-03-08
  • 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. – dave_thompson_085 Mar 9 '17 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. – sorontar Mar 9 '17 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. – Stéphane Chazelas Mar 10 '17 at 9:39
2

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"
1

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. – Zumo de Vidrio Mar 9 '17 at 8:25
0

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> ) { 
    chomp; 
    print Time::Piece -> strptime ( $_, "%Y-%m-%dT%H:%M:%SZ")->strftime("%Y-%m-%d");
}

__DATA__
2017-03-08T19:41:26Z

And as a one liner:

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

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.