# Solved: Missing Operator in Batch Script



## bassmadrigal (Dec 16, 2009)

I am working on writing a batch script in Windows XP (hopefully it will work in Vista since we will be moving to that in a couple of months) that will generate the ordinal date (in the military it is known as a Julian date). The script is complete and works properly if I invoke each line by itself in the prompt. The issue I am running into, is when I actually launch the .bat file, it gives an error about a missing operator. I did some digging and found it kicks that error out on this line


```
SET /a LeapYr=%Year% % 4 + %Year% % 100 + %Year% % 400
```
Prior to this, the %Year% variable is getting set by


```
SET Today=%Date: =0%
SET Year=%Today:~-4%
```
If I use the above code and directly paste it into the command prompt, it works fine. If I try and launch it through the script it will not process the line and kicks out the error "Missing Operator".

Luckily this doesn't affect me right now, because it isn't a leap year, but I would really like to figure out why this is working when I do it directly, but when called in a script is not working.

Does anyone have any insight they can share?

Thanks
Jeremy


----------



## TheOutcaste (Aug 8, 2007)

Percent symbols other than those surrounding a variable must be doubled when run in a batch file. The command processor scans the line looking for percent symbols. When it finds one, it removes it and notes the position. It then continues scanning for percent symbols, but if the next symbol is a percent symbol, it skips it.
When done, it assumes the text between the marked positions are variable names and replaces them with their value.
That's why the error line didn't show any percent symbols.
So this line:

```
SET /a LeapYr=[COLOR=DarkRed]%Year%[/COLOR] [COLOR=Blue]% 4 + %[/COLOR]Year[COLOR=DarkRed]% %[/COLOR] 100 + [COLOR=Blue]%Year%[/COLOR] [COLOR=Red]%[/COLOR] 400
```
 is seen like this:

```
Variable [COLOR=DarkRed]%Year%[/COLOR]  - 2009
Text             - {space}
Variable [COLOR=Blue]% 4 + %[/COLOR] - undefined (Nul)
Text             - Year
Variable [COLOR=DarkRed]% %[/COLOR]     - undefined (Nul)
Text             - {space}100{space}+{space}
Variable [COLOR=DarkRed]%Year%[/COLOR]  - 2009
Text             - {space}{space}400
```
The % followed by a space is just ignored as there are no other percent symbols on the line.

So use this to run from a file:

```
SET /a _LeapYr=%_Year% %% 4 + %_Year% %% 100 + %_Year% %% 400
```
Here's an oft used Julian Date conversion routine you can use to compare your output with to check for accuracy:

```
:_JDate
:: Convert date to Julian
:: Arguments : YYYY MM DD
:: Returns   : Julian date in _JDate
:: Usage
:: Call :JDate %_GYear% %_GMonth% %_GDay%
:: First strip leading zeroes; a logical error in this
:: routine was corrected with help from Alexander Shapiro
:: Code taken from datediff.bat written by Rob van der Woude
:: http://www.robvanderwoude.com
:: Modified to handle months and days without leading zeros
:: By TheOutcaste http://forums.techguy.org
Set _JMM=%2
Set _JDD=%3
IF 1%_JMM% LSS 110 Set _JMM=%_JMM:~-1%
IF 1%_JDD% LSS 110 Set _JDD=%_JDD:~-1%
::
:: Algorithm based on Fliegel-Van Flandern
:: algorithm from the Astronomical Almanac,
:: provided by Doctor Fenton on the Math Forum
:: (http://mathforum.org/library/drmath/view/51907.html),
:: and converted to batch code by Ron Bakowski.
Set /A _JMonth1 = ( %_JMM% - 14 ) / 12
Set /A _JYear1  = %1 + 4800
Set /A _JDate  = 1461 * ( %_JYear1% + %_JMonth1% ) / 4 + 367 * ( %_JMM% - 2 -12 * %_JMonth1% ) / 12 - ( 3 * ( ( %_JYear1% + %_JMonth1% + 100 ) / 100 ) ) / 4 + %_JDD% - 32075
For %%A In (_JMonth1 _JYear1) Do Set %%A=
Goto:EOF
```


----------



## bassmadrigal (Dec 16, 2009)

Thank you so much. So is the percent symbol similar to the backslash in bash to escape characters, or is it only used when dealing with a percent sign?

And thanks for the script, but this was all I needed to finish mine. Although mine definitely uses more code, I feel it is easier for people to understand (namely me).



```
SET Now=%Time: =0%
SET Hours=%Now:~0,2%
SET Minutes=%Now:~3,2%
SET HHMM=%hours%%minutes%
 
SET Today=%Date: =0%
SET Year=%Today:~-4%
SET Month=%Today:~-10,2%
SET Day=%Today:~-7,2%
 
SET /a LeapYr=%Year% %% 4 + %Year% %% 100 + %Year% %% 400
 
If /i %Month% GEQ 01 SET ORD=0
If /i %Month% GEQ 02 SET /a ORD=%ORD%+31
If /i %LeapYr% EQU 0 SET /a ORD=%ORD%+1
If /i %Month% GEQ 03 SET /a ORD=%ORD%+28
If /i %Month% GEQ 04 SET /a ORD=%ORD%+31
If /i %Month% GEQ 05 SET /a ORD=%ORD%+30
If /i %Month% GEQ 06 SET /a ORD=%ORD%+31
If /i %Month% GEQ 07 SET /a ORD=%ORD%+30
If /i %Month% GEQ 08 SET /a ORD=%ORD%+31
If /i %Month% GEQ 09 SET /a ORD=%ORD%+31
If /i %Month% GEQ 10 SET /a ORD=%ORD%+30
If /i %Month% GEQ 11 SET /a ORD=%ORD%+31
If /i %Month% GEQ 12 SET /a ORD=%ORD%+30
 
REM To remove the leading 0 on the day if it exists
IF %Day:~0,1% EQU 0 SET Day=%Day:~1%
 
SET /a ORD=%ORD%+%DAY%
```


----------



## TheOutcaste (Aug 8, 2007)

The escape character for batch is the caret ^.

The need to double percent signs is because batch doesn't require you to pre-define or DIM variables, so you can't tell if the text between two percent signs is an undefined variable, or just text. Easier to just remove the percent signs and double them if you really want one.

Writing, testing, and debugging your own is a great way to learn. And the script I posted calculates a totally different day, so wouldn't be useful for you.

The Ordinal date and Julian date aren't the same thing, though you'll often see people calling the Ordinal date a Julian date.
The Julian date is actually the number of days since January 1, 4713 BC (Ordinal -4712-001) on the Julian proleptic calendar:

Today is Wed Dec, 16, 2009
Ordinal Date = 2009-350
Julian date = 2,455,182


----------



## bassmadrigal (Dec 16, 2009)

Yes, I knew that about ordinal date. Unfortunately in the military, they like to, incorrectly, call it Julian date. This could explain why nothing seemed to make sense in the script you posted. I just figured it was some crazy algorithm that someone came up with to calculate it.

But thanks again for your help. I am very familiar with bash programming, but going from that to batch, is quite strange.


----------



## Squashman (Apr 4, 2003)

bassmadrigal said:


> And thanks for the script, but this was all I needed to finish mine. Although mine definitely uses more code, I feel it is easier for people to understand (namely me).


What happens when your batch file is run on a computer with the date setting like this.

```
C:\WINDOWS\system32>echo %date%
Thu 12/17/09
```


----------



## bassmadrigal (Dec 16, 2009)

Squashman said:


> What happens when your batch file is run on a computer with the date setting like this.
> 
> ```
> C:\WINDOWS\system32>echo %date%
> ...


That is currently how my date is shown. I haven't tried it with any other formats, but this script is for use on a computer run by the US Military, so it is very unlikely that the date format will change. I guess that would make it a bit more difficult if others wanted to use the script to calculate the ordinal date. I am just not sure of anyway to make the date always be a certain format. I know you can do it in bash programming, but I haven't looked into it with batch.

But currently the script will start at the end of the string and counts backwards to store the variables.


----------



## TheOutcaste (Aug 8, 2007)

Squashman said:


> ```
> C:\WINDOWS\system32>echo %date%
> Thu 12/17/09
> ```


You just wanted me to update my Date routine, didn't you?

bassmadrigal, if your system shows a two digit year, then your script sets these values:
Year=7/09
Month=u0
Day=2/


```
C:\Test>Echo %date%
Thu 12/17/09
C:\Test>test
C:\Test>SET Now=22:21:09.03
C:\Test>SET Hours=22
C:\Test>SET Minutes=21
C:\Test>SET HHMM=2221

C:\Test>SET Today=Thu012/17/09
C:\Test>SET Year=7/09
C:\Test>SET Month=u0
C:\Test>SET Day=2/

C:\Test>SET /a LeapYr=7/09 % 4 + 7/09 % 100 + 7/09 % 400
Divide by zero error.

C:\Test>If /I u0 GEQ 01 SET ORD=0

C:\Test>If /I u0 GEQ 02 SET /a ORD=0+31
0 was unexpected at this time.

C:\Test>If /i  EQU 0 SET /a ORD=31+1

C:\Test>
```
You're system must be set to display a 4 digit year, or the script won't run.

I have a routine that will get the date regardless of your international settings, and now checks for a 2 digit year and corrects it if necessary, using the *TwoDigitYearMax* setting or the default if not set.
Got one to get the time as well and returns it in 24 hour format regardless of system settings, though I'd expect your systems are already set to the 24 hour format, so that wouldn't be needed.
Adding them to your script gives this:

```
@Echo Off
Call :_GetDate
Call :_GetTime

SET Hours=%HHMM:~0,2%
SET Minutes=%HHMM:~2,2%
 
SET Year=%_fdate:~0,4%
SET Month=%_fdate:~4,2%
SET Day=%_fdate:~6,2%
 
SET /a LeapYr=%Year% %% 4 + %Year% %% 100 + %Year% %% 400
 
If /i %Month% GEQ 01 SET ORD=0
If /i %Month% GEQ 02 SET /a ORD=%ORD%+31
If /i %LeapYr% EQU 0 SET /a ORD=%ORD%+1
If /i %Month% GEQ 03 SET /a ORD=%ORD%+28
If /i %Month% GEQ 04 SET /a ORD=%ORD%+31
If /i %Month% GEQ 05 SET /a ORD=%ORD%+30
If /i %Month% GEQ 06 SET /a ORD=%ORD%+31
If /i %Month% GEQ 07 SET /a ORD=%ORD%+30
If /i %Month% GEQ 08 SET /a ORD=%ORD%+31
If /i %Month% GEQ 09 SET /a ORD=%ORD%+31
If /i %Month% GEQ 10 SET /a ORD=%ORD%+30
If /i %Month% GEQ 11 SET /a ORD=%ORD%+31
If /i %Month% GEQ 12 SET /a ORD=%ORD%+30
 
REM To remove the leading 0 on the day if it exists
IF %Day:~0,1% EQU 0 SET Day=%Day:~1%
 
SET /a ORD=%ORD%+%DAY%
Echo Ordinal date=%Year%-%ORD%
Echo Time=%HHMM%
Goto :EOF
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::                    Subroutines
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:_GetDate
:: This batch file will always display the same results,
:: independent of "International" settings.
:: This batch file uses REG.EXE from the NT Resource Kit
:: (already installed with WinXP and Vista)
:: to read the "International" settings from the registry.
:: Date is returned as yyyymmdd in variable _fdate
:: Modified by TheOutcaste http://forums.techguy.org from
:: SortDate Written byRob van der Woude http://www.robvanderwoude.com
:: to check for two digit years
::
:: If passed a parameter use that for the date
If "%~1"=="" (Set _Date=%date%) Else Set _Date=%~1
If "%_Date%A" LSS "A" (Set _NumTok=1-3) Else (Set _NumTok=2-4)
Set _TDYM=
:: Default Delimiter of TAB and Space are used
For /F "TOKENS=2*" %%A In ('REG QUERY "HKCU\Control Panel\International" /v iDate') Do Set _iDate=%%B
For /F "TOKENS=2*" %%A In ('REG QUERY "HKCU\Control Panel\International" /v sDate') Do Set _sDate=%%B
IF %_iDate%==0 For /F "TOKENS=%_NumTok% DELIMS=%_sDate% " %%B In ("%_Date%") Do Set _fdate=%%D%%B%%C
IF %_iDate%==1 For /F "TOKENS=%_NumTok% DELIMS=%_sDate% " %%B In ("%_Date%") Do Set _fdate=%%D%%C%%B
IF %_iDate%==2 For /F "TOKENS=%_NumTok% DELIMS=%_sDate% " %%B In ("%_Date%") Do Set _fdate=%%B%%C%%D
If "%_fdate:~7,1%"=="" For /F "Tokens=3 skip=3" %%I In ('reg query "HKCU\Control Panel\International\Calendars\TwoDigitYearMax" /V 1 2^>Nul') Do Set _TDYM=%%I
If Defined _TDYM (Set _MaxY=%_TDYM:~2%&Set _Cent=%_TDYM:~0,2%) Else (Set _MaxY=29&Set _Cent=20)
Set /A _Cm1=_Cent-1
If "%_fdate:~7,1%"=="" If %_fdate:~0,2% LEQ %_MaxY% (Set _fdate=%_Cent%%_fdate%) Else (Set _fdate=%_Cm1%%_fdate%)
Goto:EOF
:_GetTime
:: This batch file will always display the same results,
:: independent of "International" settings.
:: This batch file uses REG.EXE from the NT Resource Kit
:: (already installed with WinXP and Vista)
:: to read the "International" settings from the registry.
:: Time is returned in 24 hour format as hhmm in variable HHMM
:: Modified by TheOutcaste http://forums.techguy.org from SortTime
:: Written by Rob van der Woude http://www.robvanderwoude.com
::
For /F "TOKENS=*" %%A In ('TIME/T') Do Set _Time=%%A
:: Default Delimiter of TAB and Space are used
For /F "TOKENS=2*" %%A In ('REG QUERY "HKCU\Control Panel\International" /v iTime') Do Set _iTime=%%B
For /F "TOKENS=2*" %%A In ('REG QUERY "HKCU\Control Panel\International" /v sTime') Do Set _sTime=%%B
IF %_iTime%==1 Goto _in24format
For /F "TOKENS=1* DELIMS=%_sTime% " %%A In ('Echo %_Time%') Do (
    Set _Hour=%%A
    Set _Minutes=%%B
)
Set _AMPM=
Echo.%_Minutes%| FIND /I "A" >NUL && Set _AMPM=A
Echo.%_Minutes%| FIND /I "P" >NUL && Set _AMPM=P
IF [%_AMPM%]==[] Set _AMPM=A
Set _Minutes=%_Minutes:~0,2%
If %_Hour:~0,1%==0 Set _Hour=%_Hour:~-1%
IF %_Hour% LSS 12 IF /I %_AMPM%==P Set /A _Hour=%_Hour%+12
IF %_Hour% LSS 10 IF /I %_AMPM%==A Set _Hour=0%_Hour%
IF %_Hour% EQU 12 IF /I %_AMPM%==A Set _Hour=00
Set _Time=%_Hour%%_sTime%%_Minutes%
:_in24format
For /F "TOKENS=1,2* DELIMS=%_sTime% " %%A In ('Echo %_Time%') Do Set HHMM=%%A%%B
Goto:EOF
```


----------



## bassmadrigal (Dec 16, 2009)

Ugh... That is a lot for me to dig through so I can actually understand it. I will have to do it sometime when I am not trying to get out early (we have our Christmas Party tonight, and we are hoping to leave around lunch time). But you are correct, currently the script requires the date to be with MM/DD/YYYY at the end of the string. And I believe the time is just based on military time. Out of curiosity, does your script take into account European style dates? (ie. DD/MM/YYYY) I didn't look through it too much, but I would guess that it probably doesn't, due to the logistics of finding that information in the OS, which I assume isn't readily available in an environment variable. (One of the benefits of bash, you can have it output the date in whatever format you want.)

As it stands right now, I will probably leave the script as is, just so it is easy for people to modify for the new fiscal years and such, but I am trying to become more proficient at batch anyways, so I will probably dig through this and figure out how everything works. And it looks like this will also allow me to learn how to do functions in batch.

I am kinda surprised this forum doesn't stretch the code boxes to a wider width. It makes it kinda hard to read the codelines that stretch across (another reason to do this at home with notepad++).


----------



## TheOutcaste (Aug 8, 2007)

The Getdate routine reads the registry to find the date separator (/ for example) and which of the three formats is being used.
0=MM-DD-YYYY
1=DD-MM-YYYY
2=YYYY-MM-DD
Then it knows how to parse the Date output to get the right pieces to convert it to YYYYMMDD
Not as simple as a format date function in most other languages, but it can be done.

The Time Routine checks the time separator  for example), checks if in 12 or 24 hour format, converts if needed, and returns the time as HHMM in 24 hour format.

Dig though it and if you have any questions I'll be glad to help.

You can widen the code window width in the *My Account* settings. Link at the top of each page.
Under *Your Control Panel* on the left, click *Edit Options* under *Settings and Options*.
Scroll down to *Thread Display Options*, and change the *Code Block Width* setting.

One little bug, if you set it too wide, you can sometimes get the text in the first post or two hidden under the advertisement on the right if there is a wide Code box in the thread. Clicking the Reply button will switch to the Advanced Editor mode and you can then read it below the reply window.

Definitely much easier to read in Notepad++ though.

Enjoy the party, and Happy Holidays!


----------



## Squashman (Apr 4, 2003)

TheOutcaste said:


> You just wanted me to update my Date routine, didn't you?


Do you have anything better to do at 2am!


----------

