4

This was inspired by another post on Arqade, on how to backup some files using a batch script.

The following block creates a variable with the current date/time string:

REM -- Get the current date/time stamp
set DS=%date%
set TS=%time: =0%
set mm=%DS:~4,2%
set dd=%DS:~7,2%
set yyyy=%DS:~10,4%
set hh=%TS:~0,2%
set min=%TS:~3,2%
set ss=%TS:~6,2%
set ms=%TS:~9,2%
set DT_STAMP=%yyyy%-%mm%-%dd%_%hh%.%min%.%ss%

As a script writer, it is often convenient to consolidate these down to a single line. However, in this case, condensing this to a single line is extremely difficult, if not impossible.

There does not seem to be a way to put multiple set commands on a single line. Separating commands with & or && works fine, but commands on the right cannot be dependent on variables that were set earlier on the same line.

Also, notice how the time variable must have spaces replaced with zeros 0. There does not seem to be a way to do both a string replacement and get a sub-string on the same line.

Is there any way to get this down to one line? The closest I can get is two lines:

set DS=%date% && set TS=%time: =0%
set DT_STAMP=%DS:~10,4%-%DS:~4,2%-%DS:~7,2%_%TS:~0,2%.%TS:~3,2%.%TS:~6,2%

Update

This has gathered some good answers; allow me to clarify what an accepted answer must have:

  • An ugly, but re-usable, single line solution is OK (the pretty version is already provided above)
  • Standard shell commands only (PowerShell and similar must be avoided; if I wanted those, I'd just do the whole thing in PowerShell)
  • Ability to format the date in any logical format (should be able to do anything the pretty version above can do, not just support ISO formatted dates using shorthand notation)
  • It is OK to ignore localization settings (really, just this one time!)
  • Must be on a single line! (for instance, delayed expansion is handy, but can have nasty side-effects, is not re-usable in every context, and usually requires multiple lines)
JonathanDavidArndt
  • 2,518
  • 13
  • 37
  • 49
  • writing this in one line doesn't make it more readable – phuclv Jan 08 '18 at 02:32
  • The first large block of code is the readable one. The challenge I am trying to solve is getting the whole thing on a reusable, single line (with no side effects). – JonathanDavidArndt Jan 08 '18 at 02:34
  • 1
    A one-line solution might look like a desirable thing for programmers, but if it isn't readable any more it doesn't help in the long run. I'd prefer a readable subroutine with `call :GetFormattedDate` or an "external" solution with `call GetFormattedDate.bat` – Stephan Feb 15 '18 at 16:52

4 Answers4

3

It's trivial with a FOR /F statement, but as npocmaka already said, it depends on localization.

I assume your date/time looks like

Fri 09/14/2018
 8:15:46.12

Then this works

for /F "tokens=1-7 delims=/:,. " %%1 in ("%date% %time: =0%") do set DT_STAMP=%%4-%%2-%%3_%%5.%%6.%%7

But for german localization you need

for /F "tokens=1-7 delims=/:,. " %%1 in ("%date% %time: =0%") do set DT_STAMP=%%3-%%2-%%1_%%4h%%5m%%6s

Solution with a batch macro

You could build a batch macro, the use of macros is more readable, but the macros itself are a bit more complicated

Using the macro, it assigns the current timestamp to the variable myDT1:

%@AssignTimeStamp% myDT1
echo %myDT1%

The definition of the macro (delayed expansion has to be disabled while defining the macro):

REM *** Macro definition, be sure that there aren't any trailing whitespaces
set ^"LF=^
%= This creates a variable containing a single linefeed (0x0A) character =%
^"
:: Define %\M% to effectively issue a newline with line continuation
set ^"\M=^^^%LF%%LF%^%LF%%LF%^^"

set @AssignTimeStamp=for %%. in (1 2) do if %%.==2 (%\M%
    %= The next line builds the timestamp =%%\M%
    for /F "tokens=1-7 delims=/:,. " %%1 in ("%date% %time: =0%") do set timestamp=%%4-%%2-%%3_%%5h%%6m%%7s%\M%
    %= When a variable name was give then store the timestamp there, else output the timestamp =%%\M%
    for /F "tokens=1,2 delims=, " %%1 in ("#,!argv!") do ( %\M%
        for %%V in (!timestamp!) do endlocal^&if "%%~2" neq "" (set "%%2=%%V") else echo ##%%V%\M%
    )%\M%
) else setlocal enableDelayedExpansion^&set argv=,
jeb
  • 78,592
  • 17
  • 171
  • 225
  • This is why offering a bounty was worthwhile. The simple things you don't think of! Continuing my own research revealed similar answers from [Matthew Johnson](https://stackoverflow.com/a/3859042/2657515) and [John Langstaff](https://stackoverflow.com/a/16264795/2657515). (Thank you jeb for writing this up, and thank you, The Sidebar, for pointing out the related question.) – JonathanDavidArndt Sep 14 '18 at 12:00
2

Yes you can - with delayed expansion:

setlocal enableDelayedExpansion&set "DS=%date%"&& set "TS=%time: =0%"&set "DT_STAMP=!DS:~10,4!-!DS:~4,2!-!DS:~7,2!_!TS:~0,2!.!TS:~3,2!.!TS:~6,2!"

But this a bad way to format the date time as it depends on localization settings.Why you want to do this on one line? For settings independent approach check this.

npocmaka
  • 55,367
  • 18
  • 148
  • 187
2

It appears that you want an ISO-8601-like formatted date. This will produce a correctly formatted date regardless of the regional/culture settings of the system. If this is used inside a .bat script file, double the % characters on the d variable.

FOR /F %d IN ('powershell -NoLogo -NoProfile -Command Get-Date -UFormat %Y-%m-%d_%H.%M.%S') DO (SET "DT_STAMP=%d")

Or, directly in PowerShell:

$DT_STAMP = Get-Date -UFormat %Y-%m-%d_%H.%M.%S
lit
  • 14,456
  • 10
  • 65
  • 119
2

This can be done as requested using the dynamic environment variables DATE and TIME with date and time being region/country dependent using the command line:

@set "DT_STAMP=%DATE:~10,4%-%DATE:~4,2%-%DATE:~7,2%_%TIME:~0,2%.%TIME:~3,2%.%TIME:~6,2%" & call set "DT_STAMP=%%DT_STAMP: =0%%"

It is expected by this command line that DATE has a date string like Sat 09/15/2018 and TIME has a time string like  4:18:23,56 and is in 24 hours format.

The environment variable DT_STAMP has value 2018-09-15_04.18.23 for the given example date and time strings.

Better would be using the command line:

@set "DT_STAMP=%DATE:~-4%-%DATE:~-10,2%-%DATE:~-7,2%_%TIME:~0,2%.%TIME:~3,2%.%TIME:~6,2%" & call set "DT_STAMP=%%DT_STAMP: =0%%"

This second version has the advantage that it does not matter if date string starts with weekday or not. But date string must have the format MM/dd/yyyy (MDY) with any type of separator like / or . or -.

But most countries use dd.MM.yyyy (DMY) (with different separator) which would require a small modification:

@set "DT_STAMP=%DATE:~-4%-%DATE:~-7,2%-%DATE:~-10,2%_%TIME:~0,2%.%TIME:~3,2%.%TIME:~6,2%" & call set "DT_STAMP=%%DT_STAMP: =0%%"

See also What does %date:~-4,4%%date:~-10,2%%date:~-7,2%_%time:~0,2%%time:~3,2% mean?

The replacement of all spaces by 0 is done on same command line by using a second SET command which references the environment variable DT_STAMP with %% on both sides instead of just %.

The two %% are replaced by cmd.exe during parsing the command line to % before executing the first SET command. So the command line finally executed is for example:

@set "DT_STAMP=2018-09-15_ 4.18.23" & call set "DT_STAMP=%DT_STAMP: =0%"

The usage of CALL results in a second parsing of the command line part after operator & by cmd.exe. Therefore the second SET has as argument "DT_STAMP=2018-09-15_04.18.23" and the environment variable DT_STAMP gets assigned finally the wanted date/time string.

But better would be getting the current local date/time string in wanted format independent on region/country setting which is possible also with a single command line:

@for /F "tokens=2 delims==." %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do @set "DT_STAMP=%%I" & call set "DT_STAMP=%%DT_STAMP:~0,4%%-%%DT_STAMP:~4,2%%-%%DT_STAMP:~6,2%%_%%DT_STAMP:~8,2%%.%%DT_STAMP:~10,2%%.%%DT_STAMP:~12,2%%"

It uses WMIC to get current local date/time region independent and once again CALL and SET to reformat the date/time string never containing a space to wanted format.

See my answer on Why does %date% produce a different result in batch file executed as scheduled task? for an explanation on how WMIC output is processed by this command line.

By the way: . is used usually as separator between file name and file extension. For that reason it would be better to use also - instead of . between hour and minute and between minute and second.

One more note: The command after call can be also any other command which uses the date/time string directly for example in a file/folder name/path.

Mofi
  • 46,139
  • 17
  • 80
  • 143
  • This is the most clear and complete answer to the original question. The other answers have all been very helpful, but it is the `call set` that was new to me, and is the insight that I was hoping for. And you even included ways to do this independent of region/country: so great! – JonathanDavidArndt Sep 19 '18 at 15:25