Time is set incorrectly after midnight - batch-file

I use the following to get the current date/time in a more readable format:
set day=%date:~4,2%
set mth=%date:~7,2%
set yr=%date:~10,4%
set hur=%time:~0,2%
set min=%time:~3,2%
set bdate=[%day%-%mth%-%yr%]-[%hur%-%min%]
This works well and outputs something like: [02-06-2020]-[22-59]
However, after midnight the time format changes from HH:MM:SS:MS to H:MM:SS:MS. That messes up the format because it includes the colon : in the time because of the characters are shifted over by one. That results in a not useful date/time information in the log file created after midnight.
Is there any way to get the time always in the format with two digits for the hour?

Usage of dynamic variables DATE and TIME
The usage of the dynamic variables DATE and TIME with string substitutions as explained very detailed by my answer on the question What does %date:~-4,4%%date:~-10,2%%date:~-7,2%_%time:~0,2%%time:~3,2% mean? is very fast, but has the disadvantage that the format of the date and the time string depends on region (country) configured for the account used on running the batch file.
The date can be without or with abbreviated weekday at beginning and without or with a comma appended after abbreviated weekday. The date string can be in the format year/month/day or month/day/year or day/month/year with / or . or - or   as separator. See also the Wikipedia article about date format by country.
The time can be in 12 or 24 hour format. The hour can be without or with a leading 0 on being less than 10.
It was not posted what is output on running echo %DATE% %TIME% before ten o'clock in the morning and at three o'clock in the afternoon to know the local date/time format.
It looks like it is possible to use the following code with : as separator in time string:
if "%TIME:~1,1%" == ":" (
set "bdate=[%DATE:~-10,2%-%DATE:~-7,2%-%DATE:~-4%]-[0%TIME:~0,1%-%TIME:~2,2%]"
) else (
set "bdate=[%DATE:~-10,2%-%DATE:~-7,2%-%DATE:~-4%]-[%TIME:~0,2%-%TIME:~3,2%]"
)
If the second character in time string is a colon, the condition is true and the first expression is used with 0 added left to single digit hour, otherwise the ELSE block is used with the two digit hour.
Another solution with separator in time string being either a colon or a dot or a space would be:
for /F "tokens=1,2 delims=:. " %%I in ("%TIME%") do set "Hour=0%%I" & set "Minute=%%J"
set "bdate=[%DATE:~-10,2%-%DATE:~-7,2%-%DATE:~-4%]-[%Hour:~-2%-%Minute%]"
The hour left to : or . or   is assigned to environment variable Hour with a leading zero. The minute right to : or . or   is assigned to variable Minute. The date/time string is built with taking only the last two digits of Hour to have the hour finally always with two digits in date/time string assigned to environment variable bdate.
Usage of WMIC to get current date/time
Much better would be getting current date and time region (country) independent and reformat them to wanted format using string substitutions.
A region independent date/time string can be get using Windows Management Instrumentation Command line tool WMIC.
The command line
wmic OS GET LocalDateTime /VALUE
outputs UTF-16 Little Endian encoded for example:
LocalDateTime=20200208124514.468000+060
There are two empty lines, then the line with the current local date/time in format yyyyMMddHHmmss.microsecond±UTC offset in minutes and two more empty lines.
The data can be used with a batch code like this:
#echo off
for /F "tokens=2 delims==." %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "DateTime=%%I"
set "bdate=[%DateTime:~6,2%-%DateTime:~4,2%-%DateTime:~0,4%]-[%DateTime:~8,2%-%DateTime:~10,2%]"
Of interest is here only the date/time string between the equal sign and the decimal point which is the reason for using for /F options tokens=2 delims==. to get just 20200208124514 assigned to loop variable I of which value is assigned next to environment variable DateTime.
The environment variable DateTime is reformatted using string substitutions to get the date/time string in format [dd-MM-yyyy]-[HH-mm] resulting in environment variable bdate being defined with [08-02-2020]-[12-45].
It would be of course possible to use bdate everywhere instead of DateTime to use just one environment variable instead of two variables.
The advantage of using WMIC to get local date/time is its independence on Windows region settings and that it is working even on Windows XP. The disadvantage is that the command execution takes a quite long time (one to two seconds on Windows XP, more than 50 ms on Windows Vista a newer Windows versions) in comparison to the usage of the DATE and TIME dynamic variables of the Windows command processor which are accessed in a few microseconds.
The command FOR has problems parsing UTF-16 LE encoded Unicode output correct. It interprets the byte sequence 0D 00 0A 00 (carriage return + line-feed) of the WMIC output wrong as 0D 0D 0A, i.e. as two carriage returns and one line-feed. This results in interpreting the last two empty lines at end of WMIC output as two lines with a single carriage return as string.
That is very often a problem because the result of set "EnvironmentVariable=%%I" is with %%I expanding to just carriage return the deletion of the environment variable already defined before with the correct value.
There are multiple solutions to work around this Unicode parsing error of command FOR. It is possible to append & goto Label to exit the loop with a jump to :Label below the FOR loop once the value is assigned to the environment variable to avoid running into this problem at all.
Another solution is using if not defined DateTime set "DateTime=%%I" with set "DateTime=" above the FOR command line to make sure the command SET is executed only once.
One more solution is the one used in this code. The name of the property and its value are output on same line because of using WMIC option /VALUE. The command FOR runs the command SET because of tokens=2 only when it could split up the current line into at least two substrings (tokens) using equal sign and dot as delimiters because of delims==.. But the wrong parsed empty lines of WMIC output is for FOR just a line containing only a carriage return and therefore has no second token. For that reason the wrongly parsed empty lines are also ignored here by the command FOR.
See How to correct variable overwriting misbehavior when parsing output? and cmd is somehow writing Chinese text as output for details on parsing problem of FOR on UTF-16 Little Endian encoded output.
Usage of ROBOCOPY to get current date/time
Another solution to get current date and time region independent is using ROBOCOPY which is available since Windows Vista and Windows Server 2003 in system directory of Windows, but is by default not available on Windows XP. robocopy.exe of Windows Server 2003 can be copied to %SystemRoot%\System32 of Windows XP to use this executable also on Windows XP, but it is not available by default on Windows XP.
ROBOCOPY is executed with invalid source directory string "C:\|" and valid destination directory string . (could be also something other valid) and argument /NJH to suppress the output of the header information.
robocopy "C:\|" . /NJH
This execution produces the error message:
2020/02/08 12:45:14 ERROR 123 (0x0000007B) Accessing Source Directory C:\|\
The filename, directory name, or volume label syntax is incorrect.
The format of current date/time at beginning of second line after the empty line is region independent.
This output can be processed with:
set "bdate="
for /F "tokens=1-5 delims=/: " %%G in ('%SystemRoot%\System32\robocopy.exe "%SystemDrive%\|" . /NJH') do if not defined bdate set "bdate=[%%I-%%H-%%G]-[%%J-%%K]"
FOR starts in this case one more command process in background with %ComSpec% /c and the command line between the two ' appended as additional arguments which results in Windows being installed into C:\Windows in the execution of:
C:\Windows\System32\cmd.exe /c C:\Windows\System32\robocopy.exe "C:\|" . /NJH
The first five slash or colon or space separated strings of the first non-empty line of the error message output to handle STDOUT of the background command process captured by FOR are assigned to:
G ... year
H ... month
I ... day
J ... hour
K ... minute
The five data strings are concatenated to the date/time string in the wanted format.
But FOR would run the command SET once again for the second error message line. For that reason the environment variable bdate is explicitly undefined before running FOR. The environment variable bdate is defined first with the date/time string and is next not modified anymore on FOR processing the second non-empty line because of the additional IF condition to avoid overwriting the date/time string of interest with an unwanted string.
The advantage of the ROBOCOPY solution in comparison to the WMIC solution is its much faster execution time.
Additional information
For understanding the used commands and how they work, open a command prompt window, execute there the following commands, and read entirely all help pages displayed for each command very carefully.
echo /?
for /?
if /?
robocopy /?
set /?
wmic /?
wmic os /?
wmic os get /?
wmic os get localdatetime /?
PS: There are lots of other solutions to get current date/time in a specific format independent on country configured for the used account. All those alternative solutions can be found in the answers on:
How do I get current date/time on the Windows command line in a suitable format for usage in a file/folder name?

The problem is that you're using the %DATE% and %TIME% variable values, which contain strings which are not consistent across locales, PC's, or users.
The best advice is to retrieve the information using an alternative method, for example:
#For /F "Tokens=1-5Delims=/: " %%G In (
'""%__AppDir__%Robocopy.exe" \: . /NJH /L|"%__AppDir__%find.exe" " 123""'
)Do #Set "bdate=[%%I-%%H-%%G]-[%%J-%%K]"
The method involves generating an error message from the built-in robocopy utility. We do that by asking it to copy from a target directory named : in the root of the current drive, \. An error is returned because Windows does not allow directory names containing the character :. That error message, regardless of locale, PC, or user, always outputs a line starting with the date and time strings in a known format, yyyy/MM/dd hh:mm:ss.
Examples:
2020/02/08 01:25:04 ERROR 123 (0x0000007B) Accessing Source Directory C:\:\
The filename, directory name, or volume label syntax is incorrect.
2020/02/08 01:25:04 ОШИБКА 123 (0x0000007B) Доступ к исходной папке C:\:\
‘Ё*в*ЄбЁзҐбЄ*п ®иЁЎЄ* ў Ё¬Ґ*Ё д*©«*, Ё¬Ґ*Ё Ї*ЇЄЁ Ё«Ё ¬ҐвЄҐ ⮬*.
2020/02/08 01:25:04 ERREUR 123 (0x0000007B) Copie du fichier C:\:\
La syntaxe du nom de fichier, de répertoire ou de volume est incorrecte.
2020/02/08 01:25:04 FEHLER 123 (0x0000007B) Zugriff auf Zielverzeichnis C:\:\
Die Syntax für den Dateinamen, Verzeichnisnamen oder die Datenträgerbezeichnungist falsch.
We run our Robocopy command inside a for-loop in order that we can capture the required strings according to their known line position, format, and separator characters. We also need to account for the fact that the output consists of more than one line. To select the line, I have used the built-in find utility, and chosen to match only lines containing the string  123 as it cannot appear elsewhere in the output. Next I split the line up using the known separator characters as delimiters and choose how many delimiter separated token components to return. I choose a forward slash, /, which delimits the individual date components, a colon. :, which delimits the individual time components, and a space,  , which separates date and time from the rest of the line and each other. For your user case you only wanted five token components:
↓ ↓ ↓ ↓ ↓ delims
2020/02/08 01:25:04
↑ ↑ ↑ ↑ ↑
1 2 3 4 5 tokens
%G %H %I %J %K
The final thing I do is to put each of the components together in the required order and format, saving it as a string value within a variable named bdate. You should be able to use that value elsewhere in the script, as %bdate%, or !bdate! if delayed expansion is enabled.
To get more information of how to use a for-loop, please open a Command Prompt window and enter for /?.

set "day=%date:~4,2%"
set "mth=%date:~7,2%"
set "yr=%date:~10,4%"
set "_time=0%time%"
set "hur=%_time:~-11,2%"
set "min=%_time:~-8,2%"
set bdate=[%day%-%mth%-%yr%]-[%hur%-%min%]
You can pad time with a 0 and then do the substitution from the right side using negative numbers as the start position of the string. 1:00 would become 01-00 to match the pattern of HH-MM.

If using wmic.exe is an option, I would like to suggest this code to adjust your variable and output layout...
#echo off && setlocal enabledelayedexpansion
for /f "tokens=2delims==." %%i in ('
%__APPDIR__%\wbem\wmic OS Get LocalDateTime /value^|%__APPDIR__%findstr [0-9]')do set "dt=%%~i"
set "day=!dt:~6,2!"
set "mth=!dt:~4,2!"
set "yr=!dt:~0,4!"
set "hur=!dt:~8,2!"
set "min=!dt:~10,2!"
echo/[!day!-!mth!-!yr!]-[!hur!-!min!]
endlocal && goto :EOF
Outputs:
[08-02-2020]-[01-00]
Obs.: Consider accepting the #mofi answer

Related

Cannot manipulate parsed string from another batch file because of space and quote breaking it

I have a batch file which is calling another batch file. batch file 1 is parsing 5 parameters to batch file 2
When I parse the parameters from batch file 1 to batch file 2, it parses correctly but when I assigned those parsed parameters to use them in batch file 2, it breaks.
batch file 1:
ECHO
SET sql=SELECT MAX("Date") FROM SQ_TEST."Sample - Superstore Ran";
SET pref=W
SET num=0
SET day=Friday
SET config=SampleSuperStore.txt
CALL Z:\XXX\RunTableauRefreshAutomatic.bat %sql% %pref% %num% %day% %config%
batch file 2:
CALL C:\XXX\anaconda3\Scripts\activate.bat
SET sql=%~1
SET pref=%~2
SET num=%~3
SET day=%~4
SET config=%~5
C:\XXX\anaconda3\python.exe Z:\XXX\pMainAutomaticDB.py %sql% %pref% %num% %day% %config%
PAUSE
Response from batch file 2:
Z:\XXX>CALL
C:\XXX\anaconda3\Scripts\activate.bat
(base) Z:\XXX>SET sql=SELECT
(base) Z:\XXX>SET
pref=MAX("Date")
(base) Z:\XXX>SET num=FROM
(base) Z:\XXX>SET
day=SQ_TEST."Sample - Superstore Ran"
(base) Z:\XXX>SET config=W
(base)
Z:\XXX>C:\XXX\anaconda3\python.exe Z:\XXX\pMainAutomaticDB.py
SELECT MAX("Date") FROM DL_SQ_TEST."Sample - Superstore Ran" W
(base) Z:\XXX>PAUSE Press any
key to continue . . .
Update: When I remove the double quotes in sql, it works as expected but I need them in it.
Additionally I tried using ^ but the batch file 2 still breaks it differently
#echo off
setlocal
SET "sql=SELECT MAX("Date") FROM SQ_TEST.""Sample - Superstore Ran"";"
call :otherbatch "%sql%" two three
goto :eof
:otherbatch
#echo off
set "var=%~1"
set "var=%var:""="%"
echo one :%var%
echo two :%~2
echo thre:%~3
goto :eof
(it works the same whether calling a separate batch file or a subroutine, so I worked with the latter to show the principle.)
The trick is to ensure the parts with spaces (or other poisonous characters) are properly quoted.
Sadly, this involves some thinking, counting and trying. I see no safe way to automate this (besides a lot of code).
For this special string
SELECT MAX("Date") FROM SQ_TEST."Sample - Superstore Ran";
the string "%sql%" becomes:
"SELECT MAX("Date") FROM SQ_TEST."Sample - Superstore Ran";"
SiiiiiiiiiiiEooooSiiiiiiiiiiiiiiiEoooooooooooooooooooooooSiE
The line below shows, whether a part of the string is inside or outside of proper quoting with Start and End of quoting.
The next step is to ensure each SPACE (or any other delimiter) is quoted (inside) by adding quotes where needed (not around the spaces themselves, but around the substrings that contains them, using the positions of already present quotes).
This changes the string to :
"SELECT MAX("Date") FROM SQ_TEST.""Sample - Superstore Ran"";"
SiiiiiiiiiiiEooooSiiiiiiiiiiiiiiiESiiiiiiiiiiiiiiiiiiiiiiiESoE
Now every space is properly quoted (and you can see, it's difficult as other "unrelated" parts may change their inside/outside status). So it can be passed as a single parameter.
Of course that means, at the receiving side there are some superfluent quotes, which have to be deleted. We can easily (at least for this example) do that by replacing each "" with ":
set "var=%~1"
set "var=%var:""=%"
Note: that's very specific to the string to process, so I tried to explain step by step, what's to be done. You'll have to rethink the whole process for each specific string and I'm sure there are combinations where this approach would be useless.
You have potentially more problems than just spaces!
Poison characters like &, |, >, and < are another problem when passing parameters to a CALLed script. If the desired value is &"&" then it is impossible to pass that value as a literal string without escaping as the unquoted ^&"&" or as a quoted "&"^&"". But escaping dynamic strings is difficult / totally impractical. And sometimes a value must pass through multiple CALLs, each one needing its own round of escaping.
The situation is even worse when passing a caret - It is impossible to pass ^"^". You can double the unquoted ^, but the quoted "^" is a problem because the CALL command doubles quoted ^, and there is absolutely nothing you can do to prevent it. for example CALL ECHO ^^"^" yields ^"^^"!
For anyone doing any advanced scripting, one of the first tricks to learn is to pass values by reference instead of as literals. The calling script stores the value in a variable, and passes the name of the variable to the CALLed script. The CALLed script then uses delayed expansion to access the value. All the nasty batch problems are solved very simply :-)
modified batch file 1:
ECHO
SET sql=SELECT MAX("Date") FROM SQ_TEST."Sample - Superstore Ran";
SET pref=W
SET num=0
SET day=Friday
SET config=SampleSuperStore.txt
CALL Z:\XXX\RunTableauRefreshAutomatic.bat sql %pref% %num% %day% %config%
modified batch file 2:
setlocal enableDelayedExpansion
CALL C:\XXX\anaconda3\Scripts\activate.bat
SET sql=!%~1!
SET pref=%~2
SET num=%~3
SET day=%~4
SET config=%~5
:: I doubt this next line works properly.
C:\XXX\anaconda3\python.exe Z:\XXX\pMainAutomaticDB.py !sql! %pref% %num% %day% %config%
:: You probably need to change your python script to read the sql value from an environment
:: variable so you can then pass the value by reference just as we did with batch.
::
:: C:\XXX\anaconda3\python.exe Z:\XXX\pMainAutomaticDB.py sql %pref% %num% %day% %config%
::
:: Otherwise you will need to escape your quote literals - I believe python uses `\"`
PAUSE
One additional thing to be wary of - String literals containing ! will be corrupted if accessed while delayed expansion is enabled. So in addition to all the "problem" cases listed above, you also should pass by reference if the value may contain !.
Note that your python script also has potential issues with parameters containing " literals. I discuss that in the comments in modified batch 2.

Renaming file in cmd through Teamcity declaring a variable.

I am running this script in teamcity cmd. I need to rename the file from users to users_date
I am using two %% to declare them as cmd parameters instead of Teamcity parameters.
SET TODAY=%%DATE:/=-%%
SET FNAME=User_%%TODAY%%.txt
ren User.txt %%FNAME%%.txt
When I run this through team city I get the following error:
The syntax of the command is incorrect.
Can anyone help me get the correct command?
Probably, you'd need to use % in place of each %%.
It's possible that you also need
SET TODAY=%DATE:/=-%
SET FNAME=User_%TODAY: =0%.txt
ren User.txt %FNAME%.txt
where the : =0 converts any spaces in the string today to 0s.
All depends on the exact format in which date appears. It depends on user settings, and may be in dd/mm/yy or mm/dd/yy form; with or without a dayname and may use leading-zero suppression (which I've assumed).
SET TODAY=%DATE:/=-%
SET TODAY=%TODAY:~4%
SET FNAME=User_%TODAY: =0%.txt
ren User.txt %FNAME%.txt
To remove the dayname, this form may be required - it removes the first 4 characters of the today (originally date) string.

Loop Syntax error in MS-DOS 6.22

I'm trying a script to get the last modified date on a virtual PC running MS DOS 6.22.
But whenever I find a solution which includes a loop, it shows syntax error in the console.
Here's one of the lines that shows this :
for /d %%a in ("C:\log") do echo Modified date: %%~ta
Is dos 6.22 incompatible with the loops ? I have a lot of restrictions with this version (can't use robocopy, date, etc...)
The loop syntax is supported, but a bit limited (see also for /?).
FOR %A in (filelist) do myCmd %%A
So there are no switches at all.
As I remember in the most cases it wasn't possible to catch data with DOS.
It was really hard stuff even to split a simple string.
But if you absolutly need it (or have fun with it) you can split a string with a FOR loop into the first character and the rest by
for %%A in (/%var%) do echo %%A
But to save the first character is a bit tricky...
Or you could use edlin or debug to parse strings.
Advanced stuff like request the last modified date of a file was behind the possibilities of ms-dos.
But why you don't try it from your host computer to access the inner file system of the VM?
Then you could also use all modern technologies like cmd.exe batch

Removing date and time stamp from a string with spaces in batch

I am making a program that automatically backs up files, stores up to a maximum of five of them, and has an option to restore any of the five files. When the files are backed up, it adds a date and time stamp in the format of YYYYMMDD_HHMMSS_filename.ext. In order to restore the file, it is necessary to first cut out the date stamp.
The method I am currently using to cut the date stamp off of the beginning of the file is as follows.
set VAR=%VAR:~16%
echo %VAR%
The problem being, if the backed up file is called "20120825_140343_file name.txt", the above method will only return "file," omitting anything after the space. The spaces in the file names need to be preserved for them to be recognized by the program using them.
tl;dr I need to cut the string 20120825_140343_file name.txt into just "file name.txt", but my method just returns "file."
If delimiters or something would help I could separate the date stamp and file name with a different character, I.E. 20120825_140343-file-name.txt
Your method, though inelegant and inflexible, should work. This tells me that you are not storing the entire filename in VAR. That is why %var:~16% only results in file, and not file name.txt. I assume that you assign VAR like this somewhere:
SET VAR=%1
You'll need to either do this:
SET VAR=%1 %2
Or insert double-quotes around the file name when you call your batch file, and then set var like this to remove the quotes:
SET VAR=%~1
That should be enough to get your batch to work.
====================================================
But, to answer the question you actually asked, I'll show you a method of extracting "file name.txt" from var that will work even if there are more or even less than 16 prefix characters.
Use the for /f statement, specify the 3rd token, with underscores as a delimiter. Here is a self-contained example. (To run from the command-line, change %%x to %x.
SET VAR=20120825_140343_file name.txt
for /f "tokens=3 delims=_" %%x in ("%VAR%") do set VAR=%%x
ECHO %VAR%
Just remember, this solution will NOT fix your problem if you do not fix your code to assure your VAR variable has the entire filename in it.
Have you tried this?
set VAR="%VAR:~16%"

Strip drive letter

By using the command from a folder in D: drive
for /f "delims=" %%d in ('cd') do set pathdrv=%%d
echo %pathdrv%
I get "d:\some folder".I want to write batch commands to create autorun file in the root of the drive. Please help me to strip the drive letter "d:" so the output is "\some folder" and what extra change do i do to strip "\".
Short answer: Use the substring syntax to strip the first two characters from the %cd% pseudo-variable:
%cd:~2%
To remove the first backslash too:
%cd:~3%
This reliably works even with Unicode paths when the console window is set to raster fonts.
Longer answer, detailing some more options (none of which work well enough):
For arguments to the batch file you can use the special syntax %p1, which gives you the path of the first argument given to a batch file (see this answer).
This doesn't work the same way with environment variables but there are two tricks you can employ:
Use a subroutine:
call :foo "%cd%"
...
goto :eof
:foo
set result=%~p1
goto :eof
Subroutines can have arguments, just like batch files.
Use for:
for %%d in ("%cd%") do set mypath=%%~pd
However, both variants don't work when
The console is set to "Raster fonts" instead of a TrueType font such as Lucida Console or Consolas.
The current directory contains Unicode characters
That have no representation in the current legacy codepage (for Western cultures, CJK is a good choice that doesn't fit). Remember that in this case you'll only get question marks instead of the characters.
The problem with this is that whil environment variables can hold Unicode just fine you'll get into problems once you try setting up a command line which sets them. Every option detailed above relies on output of some kind before the commands are executed. This has the problem that Unicode isn't preserved but replaced by ?. The only exception is the substring variant at the very start of this answer which retains Unicode characters in the path even with raster fonts.
Drive letter:
%CD:~0,1%
Full drive name (incl. colon):
%CD:~0,2%

Resources