3

I have a string in format 20141225093000 which represents Dec 25, 2014 09:30:00 and I want to convert the original format to a unix timestamp format so i can do time operations on it.How would I do this in bash?

I can easily parse out the values with expr but I was hoping to be able to identify a format like YYYYmmddHHMMSS and then convert it based on that.

2
  • Any conversion is not performed by bash itself, but by your local date command. Are you targeting only platforms with GNU date, or do you need to support other, incompatible versions of date (such as MacOS's BSD-derived implementation)? Commented Oct 17, 2014 at 19:30
  • ...well, I say that; it's mostly true, but quite entirely: Very, very new versions of bash have built-in date formatting, but if you need to support anything even slightly old it's not widespread/portable yet. Commented Oct 17, 2014 at 19:31

4 Answers 4

9

With GNU date, you can convert YYYY-MM-DDTHH:MM:SS to epoch time (seconds since 1-1-1970) easily, like so:

date -d '2014-12-25T09:30:00' +%s

To do this starting without any delimiters:

in=20141225093000
rfc_form="${in:0:4}-${in:4:2}-${in:6:2}T${in:8:2}:${in:10:2}:${in:12:2}"
epoch_time=$(date -d "$rfc_form" +%s)
Sign up to request clarification or add additional context in comments.

Comments

1

You need to transform the string before calling date:

#!/bin/bash

s="20141225093000"
s=$(perl -pe 's/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/$1-$2-$3 $4:$5:$6/g' <<< "$s")
date -d "$s" +%s

Yet another way:

perl -MDate::Parse -MPOSIX -le '$s="20141225093000"; $s =~ s/(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/$1-$2-$3 $4:$5:$6/g ; print str2time($s);'
1419499800

5 Comments

If you're going to use perl, why not do the whole thing there? That way you don't need to depend on GNU date (and will work on non-GNU platforms such as MacOS)... and Perl's date-time libraries will take a format string, so you can do the actual conversion in a shorter/more readable way.
@CharlesDuffy it could be done in perl but when I see sh bash tags I limit my perl commands to one-liner and avoid using non-core modules which may not be installed.
fair 'nuff, but if you're trying to honor the bash tag, none of the functionality you used in perl is anything bash doesn't already have built-in (there's already support for regular expressions with capture and group extraction via the =~ operator and BASH_REMATCH).
@CharlesDuffy Date::Parse and POSIX are core modules so I can use :)
+1; adds something new and useful to the other answers (by being something that'll work on systems with neither GNU date nor GNU awk). :)
1

This Bash function does the conversion with builtins:

# Convert UTC datetime string (YYYY-MM-DD hh:mm:ss) to Unix epoch seconds
function ymdhms_to_epoch
{
    local -r ymdhms=${1//[!0-9]}    # Remove non-digits

    if (( ${#ymdhms} != 14 )) ; then
        echo "error - '$ymdhms' is not a valid datetime" >&2
        return 1
    fi

    # Extract datetime components, possibly with leading zeros
    local -r year=${ymdhms:0:4}
    local -r month_z=${ymdhms:4:2}
    local -r day_z=${ymdhms:6:2}
    local -r hour_z=${ymdhms:8:2}
    local -r minute_z=${ymdhms:10:2}
    local -r second_z=${ymdhms:12:2}

    # Remove leading zeros from datetime components to prevent them
    # being treated as octal values
    local -r month=${month_z#0}
    local -r day=${day_z#0}
    local -r hour=${hour_z#0}
    local -r minute=${minute_z#0}
    local -r second=${second_z#0}

    # Calculate Julian Day Number (jdn)
    # (See <http://en.wikipedia.org/wiki/Julian_day>, Calculation)
    local -r -i a='(14-month)/12'
    local -r -i y=year+4800-a
    local -r -i m=month+12*a-3
    local -r -i jdn='day+(153*m+2)/5+365*y+(y/4)-(y/100)+(y/400)-32045'

    # Calculate days since the Unix epoch (1 Jan. 1970)
    local -r -i epoch_days=jdn-2440588

    local -r -i epoch_seconds='((epoch_days*24+hour)*60+minute)*60+second'

    echo $epoch_seconds

    return 0
}

Example usage:

$ ymdhms_to_epoch '1970-01-01 00:00:00'
0
$ ymdhms_to_epoch '2014-10-18 00:10:06'
1413591006
$ ymdhms_to_epoch '2014-12-25 09:30:00'
1419499800

4 Comments

Interesting. Have you compared results against a canonical implementation? I'm curious as to whether there are other factors such as leap seconds involved.
I compared the results of this implementation against GNU 'date' for a large number of random dates and times between 1970 and 2038 on a modern Linux system. They all agreed. The GNU 'date' documentation (gnu.org/software/coreutils/manual/html_node/…) suggests that most systems do not use leap seconds. Even on a system that did use leap seconds, this implementation could be useful for the purposes suggested in the question.
You could shorten this by removing var=${var#0} and prepend them by 10#: declare -i v='36525*10#var'
Btw, I use date -S - +%s for this! But as a background co-process: Convert date time string to epoch in Bash
1

GNU awk:

gawk -v t=20141225093000 'BEGIN {gsub(/../, "& ", t); sub(/ /,"",t); print mktime(t)}'

If GNU date is not available, then it's likely GNU awk may not be. Perl probably has the highest chance of being available. This snippet uses strptime so you don't have to parse the time string at all:

perl -MTime::Piece -E 'say Time::Piece->strptime(shift, "%Y%m%d%H%M%S")->epoch' 20141225093000

1 Comment

+1: Tooling with a reasonable chance of being installed in places where GNU date is not.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.