34

I need to get a date in a specific format but can't work out how to do it.

Here is how I'm getting the date at the moment.

date -r "$timestamp" +'%Y-%m-%dT%H:%M:%S.s'

However the issue is the milliseconds has too many digits for the format I need. I need the milliseconds to be limited to 3 digits.

Any idea how I can do such a thing?

Current Workaround

Not accurate but it works. I calculate the milliseconds afterwards and then just take the first three characters of the string. Obviously this doesn't take into account round up.

date_string_one=`date -r "$timestamp" +'%Y-%m-%dT%H:%M:%S.'`
date_string_milli=`date -r "$timestamp" +'%s'`
date_string="$date_string_one"`printf "%.3s" "$date_string_milli"`
StuStirling
  • 15,601
  • 23
  • 93
  • 150
  • Calling date twice will result in different timestamps.. If the first call is 3.999 and the second is 4.000... then it will look like 3.000. Not sure if that will be an issue for you or not. – Iamiuru Apr 16 '13 at 12:38
  • I am supplying the timestamp so this is not an issue – StuStirling Apr 16 '13 at 12:48
  • 1
    Then why not just append 3 random numbers. Picking and choosing the milliseconds like you're doing is no better – glenn jackman Jul 28 '13 at 22:58
  • 1. you really should supply a sample value for $timestamp in order to avoid guesswork --2. Please specify if you are using BSD (since date -r means a filename in Gnu.) --3. the first example is mis-formated appending .s to the output. But even if you mean %s that is wrong because that is a count of the number of seconds since January 1970. – MarkHu Sep 05 '13 at 21:09
  • You might decide for an answer to accept? – Joe Apr 12 '14 at 17:07
  • A bunch of standard formats via `date`: https://zxq9.com/archives/795 – zxq9 Apr 13 '18 at 08:10

9 Answers9

61

You may simply use %3N to truncate the nanoseconds to the 3 most significant digits:

$ date +"%Y-%m-%d %H:%M:%S,%3N"
2014-01-08 16:00:12,746

or

$ date +"%F %T,%3N"
2014-01-08 16:00:12,746

testet with »GNU bash, Version 4.2.25(1)-release (i686-pc-linux-gnu)«

But be aware, that %N may not implemented depending on your target system or bash version. Tested on an embedded system »GNU bash, version 4.2.37(2)-release (arm-buildroot-linux-gnueabi)« there was no %N:

date +"%F %T,%N"
2014-01-08 16:44:47,%N
Joe
  • 3,090
  • 6
  • 37
  • 55
  • 6
    The `date` command is not a bash built-in, so the version of bash has no bearing on whether or not `%N` is implemented. GNU date implements it; non-GNU dates such as the BSD version found on OS X do not. – Mark Reed May 11 '15 at 19:23
5

Million ways to do this.. one simple way is to remove the trailing numbers...

date +'%Y-%m-%d %H:%M:%S.%N' | sed 's/[0-9][0-9][0-9][0-9][0-9][0-9]$//g'

or

date +'%Y-%m-%d %H:%M:%S.%N' | sed 's/......$//g'
Iamiuru
  • 469
  • 3
  • 6
  • Since the OP seemed fond of printf, and the main problem was mistaking %s for %N, I think the best solution is `printf "%.23s" $(date +'%Y-%m-%dT%H:%M:%S.%N')` which gave me `2013-07-26T20:23:20.116` – MarkHu Jul 27 '13 at 03:27
  • I realized the OP might be on BSD-flavored OS since the date -r on BSD references a number-of-seconds-from-the-epoch (instead of a filename like in Gnu) --see also http://stackoverflow.com/questions/8747845/how-can-i-detect-bsd-vs-gnu-version-of-date-in-shell-script --in which case, BSD lacks the %N formatting character (BSD decided to restrict itself to the strftime formatting.) – MarkHu Sep 05 '13 at 21:20
3

I can't help offering a "cheap" solution...

echo $(date +"%Y-%m-%d-%H:%M:%S.")$((RANDOM%1000))

After all, if accuracy is less important than uniqueness, as it is in my case... here you have it.

4Z4T4R
  • 2,340
  • 2
  • 26
  • 45
2
str="2013-04-05 13:33:53.180"

dateStr1=`date -d "$str" +'%Y-%m-%d %H:%M:%S.%N'`;

echo $dateStr1

dateStr2=`date -d "$dateStr1" +'%s'`

echo $dateStr2

dateStr3="$dateStr2"`echo ${dateStr1:20:3}`

echo $dateStr3

============================

[Output]
2013-04-05 13:33:53.180000000
1365140033
1365140033180

(above consider the datestr in UTC)

Derlin
  • 9,572
  • 2
  • 32
  • 53
punchoyeah
  • 173
  • 2
  • 7
2

Best way would be shelling out a python command.

$ python -c "from datetime import datetime; print datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]"
2017-02-15 15:03:03.496

You can then create an alias in you ~/.profile

$ alias datetime='python -c python -c "from datetime import datetime;...'

Or even create a shell function

datetime(){
  python -c python -c "from datetime import datetime;...
}
cevaris
  • 5,671
  • 2
  • 49
  • 34
  • For timezone awareness: `from datetime import datetime; from tzlocal import tzlocal; datetime.now(get_localzone()).strftime('%Y%m%d_%H%M%S.%N')[:-3]`. Note `tzlocal` is a non-standard module. Similar solutions exists via the non-standard `pytz` module as well. – pkfm Dec 07 '19 at 08:30
1

One problem: %s is not the date format for milliseconds, it is seconds of the current epoch. %N gives nanoseconds, I do not know of a specified date format for GNU date that gives milliseconds.

Try date --help to see all of the format options.

jim mcnamara
  • 16,005
  • 2
  • 34
  • 51
1

Can't think of a way to do it without a couple of steps, but this will get you there:

d=$(date +'%Y-%m-%d %H:%M:%S|%N')
ms=$(( ${d#*|}/1000000 ))
d="${d%|*}.$ms"
echo $d

2013-04-16 08:51:48.874

Since all the components are taken from a single call to date they'll be consistent.

William
  • 4,787
  • 2
  • 15
  • 14
0

Use printf to do the actual formatting; use date to produce the set of arguments printf needs to fill the format string.

printf '%04d-%02d-%02dT%02d:%02d:%02d.%03d' \
        $(date -r "${timestamp%.*}" +"%Y %m %d %H %M %S")\
        $(( ${timestamp#*.} / 1000 ))

In the above, I assume your timestamp looks something like "blahblahblah.728239". The result is a set of arguments for printf like 2013 04 16 11 03 17 728 to produce 2013-04-16T11:03:17.728.

chepner
  • 497,756
  • 71
  • 530
  • 681
  • 1
    The formatting with %02d doesn't work on both Linux distros (CentOS & Debian) I tried, due to leading zeroes in the time field(s) being interpreted as octal numbers. Try this: `a="2013 07 30 11 22 33 05678"; echo "$a printf as"; printf '%.4s-%.2s-%.2sT%.2s:%.2s:%.2s.%.3s' $a ; echo -e " using strings" ; printf '%04d-%02d-%02dT%02d:%02d:%02d.%03d' $a ; echo -e " using digits"` --which gave me an error `-bash: printf: 05678: invalid number`. Shorten the string by one and it doesn't error, but gives wrong results. – MarkHu Jul 31 '13 at 01:40
0

To convert a particular timestamp to a date string in specified format you can use:

TIMESTAMP=...
date -d @$TIMESTAMP +'%Y-%m-%d %H:%M:%S'

However, this command didn't work on MacOS. In order to make it work on both Linux and MacOS, I used the following:

TIMESTAMP=...
if [ "$(uname)" == "Darwin" ]; then
  # running on MacOS
  date -r $TIMESTAMP +'%Y-%m-%d %H:%M:%S'
else
  # running on Linux
  date -d @$TIMESTAMP +'%Y-%m-%d %H:%M:%S'
fi
ntoskrnl
  • 1,545
  • 1
  • 15
  • 25