File timestamps in batch files

PublishedActual
Birmingham New Street06:5006:54
London Euston08:1408:23

This is a strange one as it is useful to only a small sub-set of computer users, namely those which write batch files in Windows to optimise their workflows. One of my recent workflows has been to take a file with name XYZ.txt in directory A and move it to directory B ready for a file with the same name (XYZ.txt) to be placed in directory A. So that I can keep a backup of the files I needed to add a timestamp to the filename, this means that directory B contains 1 or more files with the name yyyyMMdd_hhmmss_XYZ.txt where:

  • yyyy = full year
  • MM = month in year
  • dd = date in month
  • hh = hour in day
  • mm = minute in hour
  • ss = seconds in minute

Example names could be 20111225_120027_XYZ.txt or 20120111_180653_XYZ.txt etc.

Initially it seemed like a simple problem to solve, I figured that I’d be able to use the system %DATE% and %TIME% properties. To test this theory I decided to use the command line to rename a file following this process:

  1. Click Start> Run
  2. Type cmd and hit enter
  3. Enter the following commands:
    Z:> mkdir test
    
    Z:> cd test
    
    Z:test> notepad XYZ
    
  4. Save the file using Notepad, this doesn’t have to have any content.
  5. Rename the file with the following:
    Z:test> ren XYZ.txt %DATE%_%TIME%_XYZ.txt
    

At this point DOS reported an error as apparently The syntax of the command is incorrect.. Knowing that the syntax is correct I guessed that there must be characters returned in %DATE% or %TIME% that cause issues. To test these variables I entered the following commands at the command line receiving the corresponding results:

Z:test> echo %TIME%
18:35:42.98

Z:test> echo %DATE%
12/01/2012

This simple test shows that both %DATE and %TIME% contain characters that are not valid for file names, i.e. / and :. My next plan of attack was to process the values to see if I could remove the reserved characters so I went to Google and searched. Eventually I came across what looked like a good solution in a post on StackOverflow which gave a full command, this command used syntax I’ve never seen before (like %DATE:~6,4%) so when it didn’t work first time I had to find out how to fix it. Essentially the StackOverflow answer assumed the American locale whereas my machine is using GB dates and times, some further searching gave the pointer to look at the help for the set command:

Z:test> set /?

The help file gave me exactly the detail I needed. I never knew that it was possible to affect the value returned by an environment variable but apparently it is and the help on set gives a few details. The operations that are supported are string substitution and sub-string.

String substitution is where some text in the original string is replaced with other text, this takes the form:

%DATE:str1=str2%

Using the above example the value of %DATE% will be returned with all instances of str1 being replaced with str2. This can be tested as follows:

Z:test> echo Original date %DATE% becomes %DATE:/=;%
Original date 12/01/2012 becomes 12;01;2012

As can be seen, all instances of / have been replaced with ; in the output text.

The second string operation which can be performed is sub-stringing, this is where a series of characters are extracted from the original text based on their positions. The syntax for sub-stringing takes the form:

%PATH:~offset[,length]%

Offset indicates the number of characters to pass over before starting the sub-string so an offset value of 3 means that the sub-string will start with the character at position 4. Length is optional and indicates how long the sub-string will be, if this value is not supplied then the remainder of the value is returned, if the length is greater than the value length then, again, the remainder of the word is returned. The values for offset and length can be negative, this adds complexity to the expression and can make it look awkward. If the offset is negative then the starting position is that number of character from the right of the value, for instance XYZ with an offset of -1 means the sub-string will start at Z. If the length is negative then the length of the substring is taken to be the remaining length of the value minus the negative length, for instance XYZ with an offset of -2 and length of -1 would return Y. It’s worth noting with negative indices that if the remaining length of the value is less than the number of negative characters then nothing will be returned. String substitution is a reasonably complex concept and is probably best illustrated with some examples:

Z:test> echo Original date %DATE% becomes %DATE:~0%
Original date 12/01/2012 becomes 12/01/2012

Z:test> echo Original date %DATE% becomes %DATE:~3%
Original date 12/01/2012 becomes 01/2012

Z:test> echo Original date %DATE% becomes %DATE:~3,2%
Original date 12/01/2012 becomes 01

Z:test> echo Original date %DATE% becomes %DATE:~3,7%
Original date 12/01/2012 becomes 01/2012

Z:test> echo Original date %DATE% becomes %DATE:~3,100%
Original date 12/01/2012 becomes 01/2012

Z:test> echo Original date %DATE% becomes %DATE:~3,-5%
Original date 12/01/2012 becomes 01

Z:test> echo Original date %DATE% becomes %DATE:~3,-10%
Original date 12/01/2012 becomes

Z:test> echo Original date %DATE% becomes %DATE:~3,-2%
Original date 12/01/2012 becomes 01/20

Z:test> echo Original date %DATE% becomes %DATE:~-7%
Original date 12/01/2012 becomes 01/2012

Z:test> echo Original date %DATE% becomes %DATE:~-7,2%
Original date 12/01/2012 becomes 01

Z:test> echo Original date %DATE% becomes %DATE:~-7,100%
Original date 12/01/2012 becomes 01/2012

Z:test> echo Original date %DATE% becomes %DATE:~-7,-5%
Original date 12/01/2012 becomes 01

Z:test> echo Original date %DATE% becomes %DATE:~-7,-10%
Original date 12/01/2012 becomes

Taking all the above into consideration I finally came up with the sub-stringing required to create the timestamp I wanted in the format I wanted, here is how I achieved the timestamp rename:

Z:test> set STAMP=%DATE:~6,4%%DATE:~3,2%%DATE:~0,2%%TIME:~0,2%%TIME:~3,2%%time:~6,2%_XYZ.txt

Z:test> echo rename XYZ.txt with name %STAMP%
rename XYZ.txt with name 20120112_ 80809_XYZ.txt

Z:test> set STAMP=%STAMP: =0%

Z:test> echo rename XYZ.txt with name %STAMP%
rename XYZ.txt with name 20120112_080809_XYZ.txt

Z:test> ren XYZ.txt %STAMP%

Z:test> dir
 Volume in drive Z is XP MP 2.21w

 Directory of Z:test

12/01/2012 08:12 <DIR> .
12/01/2012 08:12 <DIR> ..
12/01/2012 08:08 0 20120112_080809_XYZ.txt
 1 File(s) 0 bytes
 2 Dir(s) 9,771,372,544 bytes free