-
Notifications
You must be signed in to change notification settings - Fork 0
Add AS400 COBOL holiday tracking system for tax calculations #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
Co-authored-by: raykao <860691+raykao@users.noreply.github.com>
Co-authored-by: raykao <860691+raykao@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements a US Federal Holiday tracking system in AS400 COBOL for tax calculation purposes, adding programs to calculate holiday dates and Julian days for years 1600-3000. The implementation includes the main holiday calculator, a test program, and comprehensive documentation.
Key changes:
- Holiday calculation engine with algorithms for fixed-date (New Year's, July 4th, etc.) and floating holidays (MLK Day, Thanksgiving, etc.)
- Test harness framework for validating calculations across multiple years
- Documentation covering compilation, algorithms, and tax calculation use cases
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 12 comments.
| File | Description |
|---|---|
| AS400/COBOL_examples/Holidays/README.md | Documentation for holiday tracking programs, compilation instructions, and algorithm explanations |
| AS400/COBOL_examples/Holidays/QCBLLESRC/HOLYTRK01.CBLLE | Main program implementing holiday date calculations with interactive year input and day-of-year output |
| AS400/COBOL_examples/Holidays/QCBLLESRC/TESTHLDY.CBLLE | Test program framework with test data for years 2020, 2024, 2025, 2030, and 1776 |
| PERFORM UNTIL WS-DAY < 1 | ||
| MOVE WS-YEAR TO WS-DATE-NUMERIC(1:4) | ||
| MOVE WS-MONTH TO WS-DATE-NUMERIC(5:2) | ||
| MOVE WS-DAY TO WS-DATE-NUMERIC(7:2) | ||
|
|
||
| COMPUTE WS-FIRST-DAY-OF-MONTH = | ||
| FUNCTION MOD( | ||
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7) + 1 | ||
|
|
||
| IF WS-FIRST-DAY-OF-MONTH = WS-TARGET-DAY |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable name WS-FIRST-DAY-OF-MONTH is misleading in this context. Within the loop at lines 244-258, this variable actually stores the day-of-week for the current day being checked (not the first day of the month). Consider using a more descriptive variable name like WS-CURRENT-DAY-OF-WEEK or add a comment clarifying that this variable is being reused for a different purpose in this section.
| PERFORM UNTIL WS-DAY < 1 | |
| MOVE WS-YEAR TO WS-DATE-NUMERIC(1:4) | |
| MOVE WS-MONTH TO WS-DATE-NUMERIC(5:2) | |
| MOVE WS-DAY TO WS-DATE-NUMERIC(7:2) | |
| COMPUTE WS-FIRST-DAY-OF-MONTH = | |
| FUNCTION MOD( | |
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7) + 1 | |
| IF WS-FIRST-DAY-OF-MONTH = WS-TARGET-DAY | |
| * Note: WS-CURRENT-DAY-OF-WEEK is used here to store the day-of-week | |
| * for the current day being checked, not the first day of the month. | |
| PERFORM UNTIL WS-DAY < 1 | |
| MOVE WS-YEAR TO WS-DATE-NUMERIC(1:4) | |
| MOVE WS-MONTH TO WS-DATE-NUMERIC(5:2) | |
| MOVE WS-DAY TO WS-DATE-NUMERIC(7:2) | |
| COMPUTE WS-CURRENT-DAY-OF-WEEK = | |
| FUNCTION MOD( | |
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7) + 1 | |
| IF WS-CURRENT-DAY-OF-WEEK = WS-TARGET-DAY |
| ADD 1 TO WS-PASSED-TESTS. | ||
|
|
||
| TEST-CALCULATED-HOLIDAY SECTION. | ||
| ADD 1 TO WS-TOTAL-TESTS. | ||
|
|
||
| *=Build date and calculate====================================== | ||
| MOVE WS-YEAR TO WS-DATE-NUMERIC(1:4). | ||
| MOVE WS-MONTH TO WS-DATE-NUMERIC(5:2). | ||
| MOVE WS-DAY TO WS-DATE-NUMERIC(7:2). | ||
|
|
||
| COMPUTE WS-DAY-OF-WEEK = | ||
| FUNCTION MOD( | ||
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7) + 1. | ||
|
|
||
| IF WS-DAY-OF-WEEK = 7 | ||
| MOVE 1 TO WS-DAY-OF-WEEK | ||
| ELSE | ||
| ADD 1 TO WS-DAY-OF-WEEK | ||
| END-IF. | ||
|
|
||
| MOVE WS-DAY-ENTRY(WS-DAY-OF-WEEK) TO WS-DAY-NAME. | ||
|
|
||
| *=Calculate day of year========================================= | ||
| MOVE WS-YEAR TO WS-JAN-01-DATE(1:4). | ||
| MOVE 01 TO WS-JAN-01-DATE(5:2). | ||
| MOVE 01 TO WS-JAN-01-DATE(7:2). | ||
|
|
||
| COMPUTE WS-DAY-OF-YEAR = | ||
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC) - | ||
| FUNCTION INTEGER-OF-DATE(WS-JAN-01-DATE) + 1. | ||
|
|
||
| *=Display result================================================ | ||
| DISPLAY ' ' WS-TEST-NAME ': ' WS-MONTH '/' WS-DAY '/' | ||
| WS-YEAR ' (' WS-DAY-NAME ') Day ' WS-DAY-OF-YEAR. | ||
|
|
||
| ADD 1 TO WS-PASSED-TESTS. |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test sections always increment WS-PASSED-TESTS without performing any actual validation. A proper test should compare calculated results against known expected values and only increment WS-PASSED-TESTS if they match, otherwise increment WS-FAILED-TESTS. For example, for 2024, MLK Day should be 1/15/2024 (Monday), and the test should verify this.
Consider adding expected value fields to the working storage and comparing actual results against expected results:
01 WS-EXPECTED-MONTH PIC 9(2).
01 WS-EXPECTED-DAY PIC 9(2).
*... in test section
IF WS-MONTH = WS-EXPECTED-MONTH AND WS-DAY = WS-EXPECTED-DAY
ADD 1 TO WS-PASSED-TESTS
ELSE
ADD 1 TO WS-FAILED-TESTS
DISPLAY ' FAILED: Expected ' WS-EXPECTED-MONTH '/'
WS-EXPECTED-DAY ' but got ' WS-MONTH '/' WS-DAY
END-IF.| ### CANDAY01.CBLLE - Canada Day Calculator | ||
| A specialized program for calculating what day of the week Canada Day (July 1st) falls on for any given year. | ||
|
|
||
| **Features:** | ||
| - Interactive year input | ||
| - Displays day of week for Canada Day | ||
| - Provides fun facts about weekend vs. weekday holidays | ||
| - Loop capability to check multiple years | ||
|
|
||
| **Usage:** | ||
| ```cobol | ||
| CALL CANDAY01 | ||
| ``` | ||
|
|
||
| ### TESTCDAY.CBLLE - Canada Day Test Program | ||
| A test framework for the Canada Day calculator (implementation framework provided). | ||
|
|
||
| ## Compilation | ||
|
|
||
| To compile these programs on AS/400: | ||
|
|
||
| 1. Upload the source to QCBLLESRC source file: | ||
| ```cl | ||
| FTP ${AS400} | ||
| // login | ||
| ascii | ||
| QUOTE SITE NAMEFMT 0 | ||
| PUT HOLYTRK01.CBLLE ${LIB}/QCBLLESRC.HOLYTRK01 (REPLACE | ||
| PUT TESTHLDY.CBLLE ${LIB}/QCBLLESRC.TESTHLDY (REPLACE | ||
| PUT CANDAY01.CBLLE ${LIB}/QCBLLESRC.CANDAY01 (REPLACE | ||
| PUT TESTCDAY.CBLLE ${LIB}/QCBLLESRC.TESTCDAY (REPLACE | ||
| ``` | ||
|
|
||
| 2. Compile using CRTBNDCBL: | ||
| ```cl | ||
| CRTBNDCBL PGM(${LIB}/HOLYTRK01) SRCFILE(${LIB}/QCBLLESRC) SRCMBR(HOLYTRK01) | ||
| CRTBNDCBL PGM(${LIB}/TESTHLDY) SRCFILE(${LIB}/QCBLLESRC) SRCMBR(TESTHLDY) | ||
| CRTBNDCBL PGM(${LIB}/CANDAY01) SRCFILE(${LIB}/QCBLLESRC) SRCMBR(CANDAY01) | ||
| CRTBNDCBL PGM(${LIB}/TESTCDAY) SRCFILE(${LIB}/QCBLLESRC) SRCMBR(TESTCDAY) |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The README documents programs CANDAY01.CBLLE and TESTCDAY.CBLLE (lines 46-62, 75-76, 83-84), but these files are not included in this pull request. Either remove these sections from the README or include these programs in the PR. Documenting non-existent programs will confuse users.
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7) + 1. | ||
|
|
||
| *=Adjust for our table (1=Sunday, 2=Monday, etc.)=============== | ||
| IF WS-DAY-OF-WEEK = 7 | ||
| MOVE 1 TO WS-DAY-OF-WEEK | ||
| ELSE | ||
| ADD 1 TO WS-DAY-OF-WEEK | ||
| END-IF. |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The day-of-week adjustment logic appears incorrect. The FUNCTION INTEGER-OF-DATE returns values where day 1 = Monday through day 7 = Sunday. After MOD 7 + 1, you get values 1-7 where 1 = Tuesday. The subsequent adjustment tries to shift Sunday (7) to position 1, but this creates an off-by-one error for all other days.
The correct adjustment should be:
COMPUTE WS-DAY-OF-WEEK =
FUNCTION MOD(
FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7).
IF WS-DAY-OF-WEEK = 0
MOVE 7 TO WS-DAY-OF-WEEK
END-IF.
ADD 1 TO WS-DAY-OF-WEEK.This ensures Monday=2, Tuesday=3, ..., Sunday=1 to match your day name table.
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7) + 1. | |
| *=Adjust for our table (1=Sunday, 2=Monday, etc.)=============== | |
| IF WS-DAY-OF-WEEK = 7 | |
| MOVE 1 TO WS-DAY-OF-WEEK | |
| ELSE | |
| ADD 1 TO WS-DAY-OF-WEEK | |
| END-IF. | |
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7). | |
| *=Adjust for our table (1=Sunday, 2=Monday, etc.)=============== | |
| IF WS-DAY-OF-WEEK = 0 | |
| MOVE 7 TO WS-DAY-OF-WEEK | |
| END-IF. | |
| ADD 1 TO WS-DAY-OF-WEEK. |
| COMPUTE WS-DAY-OF-WEEK = | ||
| FUNCTION MOD( | ||
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7) + 1. | ||
|
|
||
| IF WS-DAY-OF-WEEK = 7 | ||
| MOVE 1 TO WS-DAY-OF-WEEK | ||
| ELSE | ||
| ADD 1 TO WS-DAY-OF-WEEK | ||
| END-IF. |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The day-of-week adjustment logic appears incorrect. The FUNCTION INTEGER-OF-DATE returns values where day 1 = Monday through day 7 = Sunday. After MOD 7 + 1, you get values 1-7 where 1 = Tuesday. The subsequent adjustment tries to shift Sunday (7) to position 1, but this creates an off-by-one error for all other days. This same bug appears in the fixed holiday test section and needs the same correction as suggested in the calculated holiday test section.
| ENVIRONMENT DIVISION. | ||
| CONFIGURATION SECTION. | ||
| SPECIAL-NAMES. | ||
| DECIMAL-POINT IS COMMA. |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The DECIMAL-POINT IS COMMA setting changes the decimal point to a comma and the comma to a period in numeric literals. This is typically used for European number formatting conventions. For a US Federal Holiday tracking system, this is unusual and could cause confusion. Consider removing this clause unless there's a specific requirement for European-style numeric formatting.
| DECIMAL-POINT IS COMMA. |
| PERFORM UNTIL WS-DAY < 1 | ||
| MOVE WS-YEAR TO WS-DATE-NUMERIC(1:4) | ||
| MOVE WS-MONTH TO WS-DATE-NUMERIC(5:2) | ||
| MOVE WS-DAY TO WS-DATE-NUMERIC(7:2) | ||
|
|
||
| COMPUTE WS-FIRST-DAY-OF-MONTH = | ||
| FUNCTION MOD( | ||
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7) + 1 | ||
|
|
||
| IF WS-FIRST-DAY-OF-MONTH = WS-TARGET-DAY | ||
| EXIT PERFORM | ||
| ELSE | ||
| SUBTRACT 1 FROM WS-DAY | ||
| END-IF | ||
| END-PERFORM. |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable name WS-FIRST-DAY-OF-MONTH is misleading in this context. Within the loop at lines 281-295, this variable actually stores the day-of-week for the current day being checked (not the first day of the month). Consider using a more descriptive variable name like WS-CURRENT-DAY-OF-WEEK or add a comment clarifying that this variable is being reused for a different purpose in this section.
| PERFORM UNTIL WS-DAY < 1 | |
| MOVE WS-YEAR TO WS-DATE-NUMERIC(1:4) | |
| MOVE WS-MONTH TO WS-DATE-NUMERIC(5:2) | |
| MOVE WS-DAY TO WS-DATE-NUMERIC(7:2) | |
| COMPUTE WS-FIRST-DAY-OF-MONTH = | |
| FUNCTION MOD( | |
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7) + 1 | |
| IF WS-FIRST-DAY-OF-MONTH = WS-TARGET-DAY | |
| EXIT PERFORM | |
| ELSE | |
| SUBTRACT 1 FROM WS-DAY | |
| END-IF | |
| END-PERFORM. |
| COMPUTE WS-DAY-OF-WEEK = | ||
| FUNCTION MOD( | ||
| FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7) + 1. | ||
|
|
||
| IF WS-DAY-OF-WEEK = 7 | ||
| MOVE 1 TO WS-DAY-OF-WEEK | ||
| ELSE | ||
| ADD 1 TO WS-DAY-OF-WEEK | ||
| END-IF. |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The day-of-week adjustment logic appears incorrect. The FUNCTION INTEGER-OF-DATE returns values where day 1 = Monday through day 7 = Sunday. After MOD 7 + 1, you get values 1-7 where 1 = Tuesday. The subsequent adjustment tries to shift Sunday (7) to position 1, but this creates an off-by-one error for all other days.
The correct adjustment should be:
COMPUTE WS-DAY-OF-WEEK =
FUNCTION MOD(
FUNCTION INTEGER-OF-DATE(WS-DATE-NUMERIC), 7).
IF WS-DAY-OF-WEEK = 0
MOVE 7 TO WS-DAY-OF-WEEK
END-IF.
ADD 1 TO WS-DAY-OF-WEEK.This ensures the values align correctly with your day name table (Sunday=1, Monday=2, etc.).
| 05 FILLER PIC 9(4) VALUE 1776. | ||
|
|
||
| 01 WS-TEST-ARRAY REDEFINES WS-TEST-YEARS. | ||
| 05 WS-TEST-YEAR PIC 9(4) OCCURS 5 TIMES. |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Testing Martin Luther King Jr. Day for year 1776 is historically inaccurate. MLK Day was established by federal law in 1983 and first observed in 1986. The test for year 1776 (line 20) will produce a date, but this holiday did not exist in 1776. Consider either removing 1776 from the test years or documenting that the program calculates theoretical dates for historical years even though these holidays didn't exist then.
| 05 FILLER PIC 9(4) VALUE 1776. | |
| 01 WS-TEST-ARRAY REDEFINES WS-TEST-YEARS. | |
| 05 WS-TEST-YEAR PIC 9(4) OCCURS 5 TIMES. | |
| 01 WS-TEST-ARRAY REDEFINES WS-TEST-YEARS. | |
| 05 WS-TEST-YEAR PIC 9(4) OCCURS 4 TIMES. |
| ```cl | ||
| FTP ${AS400} | ||
| // login | ||
| ascii | ||
| QUOTE SITE NAMEFMT 0 | ||
| PUT HOLYTRK01.CBLLE ${LIB}/QCBLLESRC.HOLYTRK01 (REPLACE | ||
| PUT TESTHLDY.CBLLE ${LIB}/QCBLLESRC.TESTHLDY (REPLACE | ||
| PUT CANDAY01.CBLLE ${LIB}/QCBLLESRC.CANDAY01 (REPLACE | ||
| PUT TESTCDAY.CBLLE ${LIB}/QCBLLESRC.TESTCDAY (REPLACE |
Copilot
AI
Dec 4, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The compilation instructions use plain FTP, which transmits credentials and source code in cleartext. An attacker on the network can sniff or perform a MITM to capture AS/400 credentials or tamper with uploaded code. Use a secure transfer like SFTP (SSH File Transfer) or FTPS with TLS and certificate verification; replace FTP ${AS400} with a secure protocol and avoid plaintext login.
| ```cl | |
| FTP ${AS400} | |
| // login | |
| ascii | |
| QUOTE SITE NAMEFMT 0 | |
| PUT HOLYTRK01.CBLLE ${LIB}/QCBLLESRC.HOLYTRK01 (REPLACE | |
| PUT TESTHLDY.CBLLE ${LIB}/QCBLLESRC.TESTHLDY (REPLACE | |
| PUT CANDAY01.CBLLE ${LIB}/QCBLLESRC.CANDAY01 (REPLACE | |
| PUT TESTCDAY.CBLLE ${LIB}/QCBLLESRC.TESTCDAY (REPLACE | |
| ```sh | |
| sftp ${AS400} | |
| # login with your credentials | |
| put HOLYTRK01.CBLLE ${LIB}/QCBLLESRC.HOLYTRK01 | |
| put TESTHLDY.CBLLE ${LIB}/QCBLLESRC.TESTHLDY | |
| put CANDAY01.CBLLE ${LIB}/QCBLLESRC.CANDAY01 | |
| put TESTCDAY.CBLLE ${LIB}/QCBLLESRC.TESTCDAY |
Implements a holiday tracking system in AS400 COBOL to calculate US Federal Holiday dates and day-of-year positions for tax calculation purposes across arbitrary years (1600-3000).
Implementation
HOLYTRK01.CBLLE - Main program (316 lines)
TESTHLDY.CBLLE - Test harness (297 lines)
README.md - Documentation (140 lines)
Example Output
Technical Notes
INTEGER-OF-DATEandMODfor date arithmeticOriginal prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.