Handling Persian Leap Year 1403 in SAP with ZCL_JCAL_UTILITY

In SAP systems, there’s a problem with the Persian calendar for the year 1403: the month of Esfand is set to 29 days when it should be 30 days. This error can mess up date calculations, reports, and scheduling. To fix this, I created the ZCL_JCAL_UTILITY class in SAP ABAP. This class ensures Persian dates are handled correctly, including setting Esfand 1403 to 30 days, and acts as a temporary solution until SAP releases an official patch. Here’s why this class is helpful and how it works.

Why This Class Was Created

In the Persian calendar, Esfand should have 30 days in 1403, but SAP wrongly treats it as 29 days. This causes issues for businesses that depend on accurate dates. The ZCL_JCAL_UTILITY class corrects this by properly converting and managing dates, making sure Esfand is recognized as a 30-day month until SAP provides a permanent fix.

What the Class Does

This class provides methods to manage Persian dates in SAP. Here’s what it includes:

Convert Gregorian to Persian: Turns a Gregorian date into a Persian date (e.g., 20250320 to 1403/12/30).Convert Persian to Gregorian: Changes a Persian date back to Gregorian for system use.First Day of Month/Year: Finds the first day of a Persian month or year.Last Day of Month: Calculates the last day, ensuring Esfand 1403 is set to 30 days.Days in a Year: Counts days in a Persian year, using the correct month lengths.

These tools make sure SAP handles Persian dates correctly, especially for Esfand 1403, until an official update is available.

How It Helps

Fixes Esfand: Corrects Esfand from 29 days to 30 days in 1403.Temporary Solution: Keeps systems working until SAP releases a patch.Easy to Use: Fits smoothly into SAP processes.

The ZCL_JCAL_UTILITY class is a practical fix for businesses using Persian dates in SAP. For more details, see SAP Note 3567476. Below is the code.

CLASS zcl_jcal_utility DEFINITION
PUBLIC FINAL
CREATE PUBLIC.

PUBLIC SECTION.
CLASS-METHODS convert_gregorian_to_shamsi
IMPORTING REFERENCE(iv_date) TYPE datum
EXPORTING REFERENCE(ev_shamsi_date) TYPE char10.

CLASS-METHODS convert_shamsi_to_gregorian
IMPORTING REFERENCE(iv_shamsi_date) TYPE char10
EXPORTING REFERENCE(ev_greg_date) TYPE datum.

CLASS-METHODS first_dayof_month
IMPORTING REFERENCE(p_date) TYPE d
EXPORTING REFERENCE(p_first_days) TYPE d
EXCEPTIONS date_conversion_error.

CLASS-METHODS first_dayof_year
IMPORTING REFERENCE(p_date) TYPE d
EXPORTING REFERENCE(p_first_days) TYPE d
EXCEPTIONS date_conversion_error.

CLASS-METHODS last_dayof_month
IMPORTING REFERENCE(p_date) TYPE d
EXPORTING REFERENCE(p_last_days) TYPE d
EXCEPTIONS date_conversion_error.

CLASS-METHODS get_cal_days_inyrear IMPORTING p_date TYPE d
CHANGING p_days TYPE i.
ENDCLASS.

CLASS ZCL_JCAL_UTILITY IMPLEMENTATION.

METHOD convert_gregorian_to_shamsi.
DATA lt_g_d_m TYPE TABLE OF i.
DATA iv_gy TYPE i.
DATA iv_gm TYPE i.
DATA iv_gd TYPE i.
DATA lv_out0 TYPE i.
DATA lv_out3 TYPE i.
DATA lv_out1 TYPE i.
DATA lv_out2 TYPE i.
DATA lv_year_str TYPE char4.
DATA lv_month_str TYPE char2.
DATA lv_day_str TYPE char2.

” Initialize gregorian days per month array
lt_g_d_m = VALUE #( ( 0 )
( 31 )
( 59 )
( 90 )
( 120 )
( 151 )
( 181 )
( 212 )
( 243 )
( 273 )
( 304 )
( 334 ) ).
iv_gy = iv_date+0(4).
iv_gm = iv_date+4(2).
iv_gd = iv_date+6(2).
” Calculate initial year
lv_out0 = COND #( WHEN iv_gm > 2 THEN iv_gy + 1 ELSE iv_gy ).

” Main calculation
lv_out3 = 355666 + ( 365 * iv_gy ) +
( ( lv_out0 + 3 ) DIV 4 ) –
( ( lv_out0 + 99 ) DIV 100 ) +
( ( lv_out0 + 399 ) DIV 400 ) +
iv_gd +
lt_g_d_m[ iv_gm ].

lv_out0 = -1595 + ( 33 * ( lv_out3 DIV 12053 ) ).
lv_out3 = lv_out3 MOD 12053.
lv_out0 += ( 4 * ( lv_out3 DIV 1461 ) ).
lv_out3 = lv_out3 MOD 1461.

IF lv_out3 > 365.
lv_out0 += ( ( lv_out3 – 1 ) DIV 365 ).
lv_out3 = ( lv_out3 – 1 ) MOD 365.
ENDIF.

IF lv_out3 < 186.
lv_out1 = 1 + ( lv_out3 DIV 31 ).
lv_out2 = 1 + ( lv_out3 MOD 31 ).
ELSE.
lv_out1 = 7 + ( ( lv_out3 – 186 ) DIV 30 ).
lv_out2 = 1 + ( ( lv_out3 – 186 ) MOD 30 ).
ENDIF.

lv_year_str = lv_out0.
lv_month_str = lv_out1.
lv_day_str = lv_out2.
IF lv_out1 < 10.
lv_month_str = |0{ lv_month_str }|.
ENDIF.
IF lv_out2 < 10.
lv_day_str = |0{ lv_day_str }|.
ENDIF.
ev_shamsi_date = |{ lv_year_str }/{ lv_month_str }/{ lv_day_str }|.
ENDMETHOD.

METHOD convert_shamsi_to_gregorian.
DATA iv_jy TYPE i.
DATA iv_jm TYPE i.
DATA iv_jd TYPE i.
DATA lv_jy TYPE i.
DATA lv_out2 TYPE i.
DATA lv_out0 TYPE i.
DATA lv_leap TYPE i.
DATA lt_sal_a TYPE TABLE OF i.
DATA lv_out1 TYPE i.
DATA lv_m TYPE numc2.
DATA lv_d TYPE numc2.

iv_jy = iv_shamsi_date(4).
iv_jm = iv_shamsi_date+5(2).
iv_jd = iv_shamsi_date+8(2).

lv_jy = iv_jy + 1595.

lv_out2 = -355668 + ( 365 * lv_jy ) +
( ( lv_jy DIV 33 ) * 8 ) +
( ( ( lv_jy MOD 33 ) + 3 ) DIV 4 ) +
iv_jd +
COND i( WHEN iv_jm < 7
THEN ( iv_jm – 1 ) * 31
ELSE ( ( iv_jm – 7 ) * 30 ) + 186 ).

lv_out0 = 400 * ( lv_out2 DIV 146097 ).
lv_out2 = lv_out2 MOD 146097.

IF lv_out2 > 36524.
lv_out2 -= 1.
lv_out0 += ( 100 * ( lv_out2 DIV 36524 ) ).
lv_out2 = lv_out2 MOD 36524.
IF lv_out2 >= 365.
lv_out2 += 1.
ENDIF.
ENDIF.

lv_out0 += ( 4 * ( lv_out2 DIV 1461 ) ).
lv_out2 = lv_out2 MOD 1461.

IF lv_out2 > 365.
lv_out0 += ( ( lv_out2 – 1 ) DIV 365 ).
lv_out2 = ( lv_out2 – 1 ) MOD 365.
ENDIF.

” Calculate leap year
lv_leap = COND #( WHEN ( lv_out0 MOD 4 = 0 AND lv_out0 MOD 100 <> 0 )
OR ( lv_out0 MOD 400 = 0 )
THEN 29
ELSE 28 ).

” Initialize days per month array with leap year consideration
lt_sal_a = VALUE #( ( 0 )
( 31 )
( lv_leap )
( 31 )
( 30 )
( 31 )
( 30 )
( 31 )
( 31 )
( 30 )
( 31 )
( 30 )
( 31 ) ).

lv_out2 += 1.
lv_out1 = 0.
DATA(lv_indx) = 1.
WHILE lv_out1 < 13 AND lv_out2 > lt_sal_a[ lv_indx + 1 ].
lv_out2 -= lt_sal_a[ lv_indx + 1 ].
lv_out1 += 1.
lv_indx += 1.
ENDWHILE.

lv_m = lv_out1 + 1.
lv_d = lv_out2.

ev_greg_date = |{ lv_out0 }{ lv_m }{ lv_d }|.
ENDMETHOD.

METHOD first_dayof_month.
DATA shamci_dt TYPE c LENGTH 10.
DATA month TYPE n LENGTH 2.
DATA year TYPE n LENGTH 4.

TRY.
convert_gregorian_to_shamsi( EXPORTING iv_date = p_date
IMPORTING ev_shamsi_date = shamci_dt ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.

month = shamci_dt+5(2).
year = shamci_dt+0(4).

CONCATENATE year ‘/’ month ‘/01’ INTO shamci_dt.

TRY.
convert_shamsi_to_gregorian( EXPORTING iv_shamsi_date = shamci_dt
IMPORTING ev_greg_date = p_first_days ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.
ENDMETHOD.

METHOD first_dayof_year.
DATA shamci_dt TYPE c LENGTH 10.
DATA year TYPE n LENGTH 4.

TRY.

convert_gregorian_to_shamsi( EXPORTING iv_date = p_date
IMPORTING ev_shamsi_date = shamci_dt ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.

year = shamci_dt+0(4).
CONCATENATE year ‘/01/01’ INTO shamci_dt.

TRY.
convert_shamsi_to_gregorian( EXPORTING iv_shamsi_date = shamci_dt
IMPORTING ev_greg_date = p_first_days ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.
ENDMETHOD.

METHOD get_cal_days_inyrear.
DATA first_days TYPE d.

first_dayof_year( EXPORTING p_date = p_date
IMPORTING p_first_days = first_days ).

CALL FUNCTION ‘HR_99S_INTERVAL_BETWEEN_DATES’
EXPORTING
begda = first_days
endda = p_date
IMPORTING
days = p_days.
ENDMETHOD.

METHOD last_dayof_month.
DATA shamci_dt TYPE c LENGTH 10.
DATA month TYPE n LENGTH 2.
DATA year TYPE n LENGTH 4.

TRY.
convert_gregorian_to_shamsi( EXPORTING iv_date = p_date
IMPORTING ev_shamsi_date = shamci_dt ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.

month = shamci_dt+5(2).
year = shamci_dt+0(4).

IF month <> ’12’.
month += 1.

ELSE.
month = ’01’.
year += 1.
ENDIF.

CONCATENATE year ‘/’ month ‘/01’ INTO shamci_dt.

TRY.
convert_shamsi_to_gregorian( EXPORTING iv_shamsi_date = shamci_dt
IMPORTING ev_greg_date = p_last_days ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.
CALL FUNCTION ‘RP_CALC_DATE_IN_INTERVAL’
EXPORTING
date = p_last_days
days = 1
months = 0
signum = ‘-‘
years = 0
IMPORTING
calc_date = p_last_days.
ENDMETHOD.
ENDCLASS.

 

 

​ In SAP systems, there’s a problem with the Persian calendar for the year 1403: the month of Esfand is set to 29 days when it should be 30 days. This error can mess up date calculations, reports, and scheduling. To fix this, I created the ZCL_JCAL_UTILITY class in SAP ABAP. This class ensures Persian dates are handled correctly, including setting Esfand 1403 to 30 days, and acts as a temporary solution until SAP releases an official patch. Here’s why this class is helpful and how it works.Why This Class Was CreatedIn the Persian calendar, Esfand should have 30 days in 1403, but SAP wrongly treats it as 29 days. This causes issues for businesses that depend on accurate dates. The ZCL_JCAL_UTILITY class corrects this by properly converting and managing dates, making sure Esfand is recognized as a 30-day month until SAP provides a permanent fix.What the Class DoesThis class provides methods to manage Persian dates in SAP. Here’s what it includes:Convert Gregorian to Persian: Turns a Gregorian date into a Persian date (e.g., 20250320 to 1403/12/30).Convert Persian to Gregorian: Changes a Persian date back to Gregorian for system use.First Day of Month/Year: Finds the first day of a Persian month or year.Last Day of Month: Calculates the last day, ensuring Esfand 1403 is set to 30 days.Days in a Year: Counts days in a Persian year, using the correct month lengths.These tools make sure SAP handles Persian dates correctly, especially for Esfand 1403, until an official update is available.How It HelpsFixes Esfand: Corrects Esfand from 29 days to 30 days in 1403.Temporary Solution: Keeps systems working until SAP releases a patch.Easy to Use: Fits smoothly into SAP processes.The ZCL_JCAL_UTILITY class is a practical fix for businesses using Persian dates in SAP. For more details, see SAP Note 3567476. Below is the code.CLASS zcl_jcal_utility DEFINITION
PUBLIC FINAL
CREATE PUBLIC.

PUBLIC SECTION.
CLASS-METHODS convert_gregorian_to_shamsi
IMPORTING REFERENCE(iv_date) TYPE datum
EXPORTING REFERENCE(ev_shamsi_date) TYPE char10.

CLASS-METHODS convert_shamsi_to_gregorian
IMPORTING REFERENCE(iv_shamsi_date) TYPE char10
EXPORTING REFERENCE(ev_greg_date) TYPE datum.

CLASS-METHODS first_dayof_month
IMPORTING REFERENCE(p_date) TYPE d
EXPORTING REFERENCE(p_first_days) TYPE d
EXCEPTIONS date_conversion_error.

CLASS-METHODS first_dayof_year
IMPORTING REFERENCE(p_date) TYPE d
EXPORTING REFERENCE(p_first_days) TYPE d
EXCEPTIONS date_conversion_error.

CLASS-METHODS last_dayof_month
IMPORTING REFERENCE(p_date) TYPE d
EXPORTING REFERENCE(p_last_days) TYPE d
EXCEPTIONS date_conversion_error.

CLASS-METHODS get_cal_days_inyrear IMPORTING p_date TYPE d
CHANGING p_days TYPE i.
ENDCLASS.

CLASS ZCL_JCAL_UTILITY IMPLEMENTATION.

METHOD convert_gregorian_to_shamsi.
DATA lt_g_d_m TYPE TABLE OF i.
DATA iv_gy TYPE i.
DATA iv_gm TYPE i.
DATA iv_gd TYPE i.
DATA lv_out0 TYPE i.
DATA lv_out3 TYPE i.
DATA lv_out1 TYPE i.
DATA lv_out2 TYPE i.
DATA lv_year_str TYPE char4.
DATA lv_month_str TYPE char2.
DATA lv_day_str TYPE char2.

” Initialize gregorian days per month array
lt_g_d_m = VALUE #( ( 0 )
( 31 )
( 59 )
( 90 )
( 120 )
( 151 )
( 181 )
( 212 )
( 243 )
( 273 )
( 304 )
( 334 ) ).
iv_gy = iv_date+0(4).
iv_gm = iv_date+4(2).
iv_gd = iv_date+6(2).
” Calculate initial year
lv_out0 = COND #( WHEN iv_gm > 2 THEN iv_gy + 1 ELSE iv_gy ).

” Main calculation
lv_out3 = 355666 + ( 365 * iv_gy ) +
( ( lv_out0 + 3 ) DIV 4 ) –
( ( lv_out0 + 99 ) DIV 100 ) +
( ( lv_out0 + 399 ) DIV 400 ) +
iv_gd +
lt_g_d_m[ iv_gm ].

lv_out0 = -1595 + ( 33 * ( lv_out3 DIV 12053 ) ).
lv_out3 = lv_out3 MOD 12053.
lv_out0 += ( 4 * ( lv_out3 DIV 1461 ) ).
lv_out3 = lv_out3 MOD 1461.

IF lv_out3 > 365.
lv_out0 += ( ( lv_out3 – 1 ) DIV 365 ).
lv_out3 = ( lv_out3 – 1 ) MOD 365.
ENDIF.

IF lv_out3 < 186.
lv_out1 = 1 + ( lv_out3 DIV 31 ).
lv_out2 = 1 + ( lv_out3 MOD 31 ).
ELSE.
lv_out1 = 7 + ( ( lv_out3 – 186 ) DIV 30 ).
lv_out2 = 1 + ( ( lv_out3 – 186 ) MOD 30 ).
ENDIF.

lv_year_str = lv_out0.
lv_month_str = lv_out1.
lv_day_str = lv_out2.
IF lv_out1 < 10.
lv_month_str = |0{ lv_month_str }|.
ENDIF.
IF lv_out2 < 10.
lv_day_str = |0{ lv_day_str }|.
ENDIF.
ev_shamsi_date = |{ lv_year_str }/{ lv_month_str }/{ lv_day_str }|.
ENDMETHOD.

METHOD convert_shamsi_to_gregorian.
DATA iv_jy TYPE i.
DATA iv_jm TYPE i.
DATA iv_jd TYPE i.
DATA lv_jy TYPE i.
DATA lv_out2 TYPE i.
DATA lv_out0 TYPE i.
DATA lv_leap TYPE i.
DATA lt_sal_a TYPE TABLE OF i.
DATA lv_out1 TYPE i.
DATA lv_m TYPE numc2.
DATA lv_d TYPE numc2.

iv_jy = iv_shamsi_date(4).
iv_jm = iv_shamsi_date+5(2).
iv_jd = iv_shamsi_date+8(2).

lv_jy = iv_jy + 1595.

lv_out2 = -355668 + ( 365 * lv_jy ) +
( ( lv_jy DIV 33 ) * 8 ) +
( ( ( lv_jy MOD 33 ) + 3 ) DIV 4 ) +
iv_jd +
COND i( WHEN iv_jm < 7
THEN ( iv_jm – 1 ) * 31
ELSE ( ( iv_jm – 7 ) * 30 ) + 186 ).

lv_out0 = 400 * ( lv_out2 DIV 146097 ).
lv_out2 = lv_out2 MOD 146097.

IF lv_out2 > 36524.
lv_out2 -= 1.
lv_out0 += ( 100 * ( lv_out2 DIV 36524 ) ).
lv_out2 = lv_out2 MOD 36524.
IF lv_out2 >= 365.
lv_out2 += 1.
ENDIF.
ENDIF.

lv_out0 += ( 4 * ( lv_out2 DIV 1461 ) ).
lv_out2 = lv_out2 MOD 1461.

IF lv_out2 > 365.
lv_out0 += ( ( lv_out2 – 1 ) DIV 365 ).
lv_out2 = ( lv_out2 – 1 ) MOD 365.
ENDIF.

” Calculate leap year
lv_leap = COND #( WHEN ( lv_out0 MOD 4 = 0 AND lv_out0 MOD 100 <> 0 )
OR ( lv_out0 MOD 400 = 0 )
THEN 29
ELSE 28 ).

” Initialize days per month array with leap year consideration
lt_sal_a = VALUE #( ( 0 )
( 31 )
( lv_leap )
( 31 )
( 30 )
( 31 )
( 30 )
( 31 )
( 31 )
( 30 )
( 31 )
( 30 )
( 31 ) ).

lv_out2 += 1.
lv_out1 = 0.
DATA(lv_indx) = 1.
WHILE lv_out1 < 13 AND lv_out2 > lt_sal_a[ lv_indx + 1 ].
lv_out2 -= lt_sal_a[ lv_indx + 1 ].
lv_out1 += 1.
lv_indx += 1.
ENDWHILE.

lv_m = lv_out1 + 1.
lv_d = lv_out2.

ev_greg_date = |{ lv_out0 }{ lv_m }{ lv_d }|.
ENDMETHOD.

METHOD first_dayof_month.
DATA shamci_dt TYPE c LENGTH 10.
DATA month TYPE n LENGTH 2.
DATA year TYPE n LENGTH 4.

TRY.
convert_gregorian_to_shamsi( EXPORTING iv_date = p_date
IMPORTING ev_shamsi_date = shamci_dt ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.

month = shamci_dt+5(2).
year = shamci_dt+0(4).

CONCATENATE year ‘/’ month ‘/01’ INTO shamci_dt.

TRY.
convert_shamsi_to_gregorian( EXPORTING iv_shamsi_date = shamci_dt
IMPORTING ev_greg_date = p_first_days ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.
ENDMETHOD.

METHOD first_dayof_year.
DATA shamci_dt TYPE c LENGTH 10.
DATA year TYPE n LENGTH 4.

TRY.

convert_gregorian_to_shamsi( EXPORTING iv_date = p_date
IMPORTING ev_shamsi_date = shamci_dt ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.

year = shamci_dt+0(4).
CONCATENATE year ‘/01/01’ INTO shamci_dt.

TRY.
convert_shamsi_to_gregorian( EXPORTING iv_shamsi_date = shamci_dt
IMPORTING ev_greg_date = p_first_days ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.
ENDMETHOD.

METHOD get_cal_days_inyrear.
DATA first_days TYPE d.

first_dayof_year( EXPORTING p_date = p_date
IMPORTING p_first_days = first_days ).

CALL FUNCTION ‘HR_99S_INTERVAL_BETWEEN_DATES’
EXPORTING
begda = first_days
endda = p_date
IMPORTING
days = p_days.
ENDMETHOD.

METHOD last_dayof_month.
DATA shamci_dt TYPE c LENGTH 10.
DATA month TYPE n LENGTH 2.
DATA year TYPE n LENGTH 4.

TRY.
convert_gregorian_to_shamsi( EXPORTING iv_date = p_date
IMPORTING ev_shamsi_date = shamci_dt ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.

month = shamci_dt+5(2).
year = shamci_dt+0(4).

IF month <> ’12’.
month += 1.

ELSE.
month = ’01’.
year += 1.
ENDIF.

CONCATENATE year ‘/’ month ‘/01’ INTO shamci_dt.

TRY.
convert_shamsi_to_gregorian( EXPORTING iv_shamsi_date = shamci_dt
IMPORTING ev_greg_date = p_last_days ).

CATCH cx_root.
MESSAGE e002(zhr_py) WITH p_date
RAISING date_conversion_error.
ENDTRY.
CALL FUNCTION ‘RP_CALC_DATE_IN_INTERVAL’
EXPORTING
date = p_last_days
days = 1
months = 0
signum = ‘-‘
years = 0
IMPORTING
calc_date = p_last_days.
ENDMETHOD.
ENDCLASS.    Read More Technology Blogs by Members articles 

#SAP

#SAPTechnologyblog

You May Also Like

More From Author