From 7e5f6da63d9c9053630f70736788216435f19948 Mon Sep 17 00:00:00 2001 From: Kjetil Kilhavn Date: Thu, 10 Aug 2023 07:11:03 +0000 Subject: [PATCH] Initial support for time stamps Cells which are formatted with both date format codes and time format codes are converted to time stamp values instead of date values. There is no support for time zones - all values will be converted to UTC time stamps. --- src/zcl_excel_common.clas.abap | 19 +++- src/zcl_excel_common.clas.testclasses.abap | 65 ++++++++++++- src/zcl_excel_worksheet.clas.abap | 51 +++++++++- src/zcl_excel_worksheet.clas.testclasses.abap | 94 ++++++++++++++++++- src/zcx_excel.clas.xml | 8 ++ 5 files changed, 229 insertions(+), 8 deletions(-) diff --git a/src/zcl_excel_common.clas.abap b/src/zcl_excel_common.clas.abap index 17263df..4cfddb3 100644 --- a/src/zcl_excel_common.clas.abap +++ b/src/zcl_excel_common.clas.abap @@ -7,6 +7,7 @@ CLASS zcl_excel_common DEFINITION *"* do not include other source files here!!! PUBLIC SECTION. + TYPES ty_timestamp TYPE p LENGTH 8 DECIMALS 0. CONSTANTS c_excel_baseline_date TYPE d VALUE '19000101'. "#EC NOTEXT CLASS-DATA c_excel_numfmt_offset TYPE int1 VALUE 164. "#EC NOTEXT . . . . . . . . . . . . . . . . " . CONSTANTS c_excel_sheet_max_col TYPE int4 VALUE 16384. "#EC NOTEXT @@ -116,6 +117,13 @@ CLASS zcl_excel_common DEFINITION VALUE(ep_value) TYPE t RAISING zcx_excel . + CLASS-METHODS excel_string_to_timestamp + IMPORTING + !ip_value TYPE zexcel_cell_value + RETURNING + VALUE(ep_value) TYPE ty_timestamp + RAISING + zcx_excel . CLASS-METHODS excel_string_to_number IMPORTING !ip_value TYPE zexcel_cell_value @@ -863,7 +871,7 @@ CLASS zcl_excel_common IMPLEMENTATION. CHECK ip_value IS NOT INITIAL AND ip_value CN ' 0'. TRY. - lv_date_int = ip_value. + lv_date_int = floor( ip_value ). IF lv_date_int NOT BETWEEN 1 AND 2958465. zcx_excel=>raise_text( 'Unable to interpret date' ). ENDIF. @@ -897,7 +905,7 @@ CLASS zcl_excel_common IMPLEMENTATION. TRY. - lv_day_fraction = ip_value. + lv_day_fraction = ip_value - floor( ip_value ). lv_seconds_in_day = lv_day_fraction * lc_seconds_in_day. ep_value = lv_seconds_in_day. @@ -908,6 +916,13 @@ CLASS zcl_excel_common IMPLEMENTATION. ENDMETHOD. + METHOD excel_string_to_timestamp. + CONVERT DATE excel_string_to_date( ip_value ) + TIME excel_string_to_time( ip_value ) + INTO TIME STAMP ep_value TIME ZONE 'UTC'. + ENDMETHOD. + + METHOD get_fieldcatalog. DATA: lr_dref_tab TYPE REF TO data, lo_salv_table TYPE REF TO cl_salv_table, diff --git a/src/zcl_excel_common.clas.testclasses.abap b/src/zcl_excel_common.clas.testclasses.abap index f6a53af..2d693fb 100644 --- a/src/zcl_excel_common.clas.testclasses.abap +++ b/src/zcl_excel_common.clas.testclasses.abap @@ -1,5 +1,8 @@ CLASS lcl_excel_common_test DEFINITION DEFERRED. -CLASS zcl_excel_common DEFINITION LOCAL FRIENDS lcl_excel_common_test. +CLASS ltc_xlsx_date_time DEFINITION DEFERRED. +CLASS zcl_excel_common DEFINITION LOCAL FRIENDS + lcl_excel_common_test + ltc_xlsx_date_time. *----------------------------------------------------------------------* * CLASS lcl_Excel_Common_Test DEFINITION @@ -131,6 +134,19 @@ CLASS lcl_excel_common_test DEFINITION FOR TESTING ENDCLASS. +CLASS ltc_xlsx_date_time DEFINITION FINAL FOR TESTING + DURATION SHORT + RISK LEVEL HARMLESS. + + PRIVATE SECTION. + CONSTANTS dt_20230803_140711 TYPE string VALUE `45141.588321759256`. "Excel raw value for date & time 2023-08-03 14:07:11 + + METHODS: + from_xlsx_to_date FOR TESTING RAISING cx_static_check, + from_xlsx_to_time FOR TESTING RAISING cx_static_check, + from_xlsx_to_timestamp FOR TESTING RAISING cx_static_check. + ENDCLASS. + *----------------------------------------------------------------------* * CLASS lcl_Excel_Common_Test IMPLEMENTATION *----------------------------------------------------------------------* @@ -1688,3 +1704,50 @@ CLASS lcl_excel_common_test IMPLEMENTATION. ENDMETHOD. ENDCLASS. + + +CLASS ltc_xlsx_date_time IMPLEMENTATION. + + METHOD from_xlsx_to_date. + TRY. + cl_abap_unit_assert=>assert_equals( + act = zcl_excel_common=>excel_string_to_date( dt_20230803_140711 ) + exp = '20230803' + msg = 'Wrong date from conversion' + level = if_aunit_constants=>critical ). + CATCH zcx_excel INTO DATA(excel_error). + cl_abap_unit_assert=>fail( + msg = 'unexpected exception' && excel_error->get_text( ) + level = if_aunit_constants=>critical ). + ENDTRY. + ENDMETHOD. + + METHOD from_xlsx_to_time. + TRY. + cl_abap_unit_assert=>assert_equals( + act = zcl_excel_common=>excel_string_to_time( dt_20230803_140711 ) + exp = '140711' + msg = 'Wrong time from conversion' + level = if_aunit_constants=>critical ). + CATCH zcx_excel INTO DATA(excel_error). + cl_abap_unit_assert=>fail( + msg = 'unexpected exception' && excel_error->get_text( ) + level = if_aunit_constants=>critical ). + ENDTRY. + ENDMETHOD. + + METHOD from_xlsx_to_timestamp. + TRY. + cl_abap_unit_assert=>assert_equals( + act = zcl_excel_common=>excel_string_to_timestamp( dt_20230803_140711 ) + exp = '20230803140711' + msg = 'Wrong time stamp from conversion' + level = if_aunit_constants=>critical ). + CATCH zcx_excel INTO DATA(excel_error). + cl_abap_unit_assert=>fail( + msg = 'unexpected exception' && excel_error->get_text( ) + level = if_aunit_constants=>critical ). + ENDTRY. + ENDMETHOD. + +ENDCLASS. diff --git a/src/zcl_excel_worksheet.clas.abap b/src/zcl_excel_worksheet.clas.abap index c650015..a1b1e7d 100644 --- a/src/zcl_excel_worksheet.clas.abap +++ b/src/zcl_excel_worksheet.clas.abap @@ -693,6 +693,7 @@ CLASS zcl_excel_worksheet DEFINITION *"* private components of class ZCL_EXCEL_WORKSHEET *"* do not include other source files here!!! TYPES ty_table_settings TYPE STANDARD TABLE OF zexcel_s_table_settings WITH DEFAULT KEY. + CONSTANTS c_typekind_timestamp TYPE abap_typekind VALUE 'Z'. DATA active_cell TYPE zexcel_s_cell_data . DATA charts TYPE REF TO zcl_excel_drawings . DATA columns TYPE REF TO zcl_excel_columns . @@ -782,6 +783,16 @@ CLASS zcl_excel_worksheet DEFINITION EXPORTING !ep_value TYPE simple !ep_value_type TYPE abap_typekind . + METHODS is_date_format + IMPORTING + !ip_value TYPE simple + RETURNING + VALUE(result) TYPE abap_bool. + METHODS is_time_format + IMPORTING + !ip_value TYPE simple + RETURNING + VALUE(result) TYPE abap_bool. METHODS move_supplied_borders IMPORTING iv_border_supplied TYPE abap_bool @@ -2167,7 +2178,7 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. EXCEPTIONS OTHERS = 3 ). - IF lt_ddic_object IS INITIAL. + IF sy-subrc = 0 AND lt_ddic_object IS INITIAL. lt_comp_view = lo_line_type->get_included_view( ). LOOP AT lt_comp_view INTO ls_comp_view. ls_comp_view-type->get_ddic_object( @@ -2176,7 +2187,7 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. EXCEPTIONS OTHERS = 3 ). - IF lt_ddic_object_comp IS NOT INITIAL. + IF sy-subrc = 0 AND lt_ddic_object_comp IS NOT INITIAL. READ TABLE lt_ddic_object_comp INTO ls_ddic_object INDEX 1. ls_ddic_object-fieldname = ls_comp_view-name. APPEND ls_ddic_object TO lt_ddic_object. @@ -2237,8 +2248,13 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. REPLACE ALL OCCURRENCES OF REGEX '\[\L[^]]*\]' IN lv_format_code WITH ''. IF lv_format_code CA 'yd' OR lv_format_code EQ zcl_excel_style_number_format=>c_format_date_std. - " DATE = yyyymmdd - ls_style_conv-abap_type = cl_abap_typedescr=>typekind_date. + "TODO kjetil-kilhavn make use of is_date_format( ) and is_time_format( ) + IF lv_format_code CA 'hs'. + ls_style_conv-abap_type = c_typekind_timestamp. + ELSE. + " DATE = yyyymmdd + ls_style_conv-abap_type = cl_abap_typedescr=>typekind_date. + ENDIF. ELSEIF lv_format_code CA 'hs'. " TIME = hhmmss ls_style_conv-abap_type = cl_abap_typedescr=>typekind_time. @@ -2295,6 +2311,8 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. READ TABLE lt_style_conv INTO ls_style_conv WITH KEY cell_style = -cell_style BINARY SEARCH. IF sy-subrc EQ 0. CASE ls_style_conv-abap_type. + WHEN c_typekind_timestamp. + = zcl_excel_common=>excel_string_to_timestamp( -cell_value ). WHEN cl_abap_typedescr=>typekind_date. = zcl_excel_common=>excel_string_to_date( -cell_value ). WHEN cl_abap_typedescr=>typekind_time. @@ -3241,6 +3259,31 @@ CLASS zcl_excel_worksheet IMPLEMENTATION. ENDMETHOD. "IS_CELL_MERGED + METHOD is_date_format. + "TODO kjetil-kilhavn figure out how to detect all valid date formats, including mmmmm (first letter of month name) + result = abap_false. + + CHECK ip_value CA 'ymd'. + + result = abap_true. + ENDMETHOD. "is_date_format + + + METHOD is_time_format. + "TODO kjetil-kilhavn try to implement this without hardcoding too much... + "Note: Open Office XML: m or mm represents minutes if it is used + " immediately after h or hh (for hours) or + " immediately before ss (for seconds) + " Source: ECMA-376 Part 1 page 1789 + " What about e.g. format code 'm:s' - s is also valid for seconds!? + result = abap_false. + + CHECK ip_value CA 'hms'. + + result = abap_true. + ENDMETHOD. "is_time_format + + METHOD move_supplied_borders. DATA: ls_borderx TYPE zexcel_s_cstylex_border. diff --git a/src/zcl_excel_worksheet.clas.testclasses.abap b/src/zcl_excel_worksheet.clas.testclasses.abap index 3493506..34a6083 100644 --- a/src/zcl_excel_worksheet.clas.testclasses.abap +++ b/src/zcl_excel_worksheet.clas.testclasses.abap @@ -6,6 +6,8 @@ CLASS ltc_normalize_style_param DEFINITION DEFERRED. CLASS ltc_check_cell_column_formula DEFINITION DEFERRED. CLASS ltc_check_overlapping DEFINITION DEFERRED. CLASS ltc_set_cell_value_types DEFINITION DEFERRED. +CLASS ltc_is_date_format DEFINITION DEFERRED. +CLASS ltc_is_time_format DEFINITION DEFERRED. CLASS zcl_excel_worksheet DEFINITION LOCAL FRIENDS ltc_normalize_column_heading ltc_normalize_columnrow_param @@ -14,7 +16,9 @@ CLASS zcl_excel_worksheet DEFINITION LOCAL FRIENDS ltc_check_overlapping ltc_normalize_style_param ltc_check_cell_column_formula - ltc_set_cell_value_types. + ltc_set_cell_value_types + ltc_is_date_format + ltc_is_time_format. CLASS lcl_excel_worksheet_test DEFINITION FOR TESTING RISK LEVEL HARMLESS @@ -301,6 +305,38 @@ CLASS ltc_set_cell_value_types DEFINITION FOR TESTING ENDCLASS. +CLASS ltc_is_date_format DEFINITION FINAL FOR TESTING + DURATION SHORT + RISK LEVEL HARMLESS. + + PRIVATE SECTION. + DATA cut TYPE REF TO zcl_excel_worksheet. + DATA xlsx TYPE REF TO zcl_excel. + + METHODS: + setup, + teardown. + METHODS: + date_format_iso FOR TESTING RAISING cx_static_check. +ENDCLASS. + + +CLASS ltc_is_time_format DEFINITION FINAL FOR TESTING + DURATION SHORT + RISK LEVEL HARMLESS. + + PRIVATE SECTION. + DATA cut TYPE REF TO zcl_excel_worksheet. + DATA xlsx TYPE REF TO zcl_excel. + + METHODS: + setup, + teardown. + METHODS: + time_format_iso FOR TESTING RAISING cx_static_check. +ENDCLASS. + + CLASS lcl_excel_worksheet_test IMPLEMENTATION. * ============================================== @@ -1664,3 +1700,59 @@ CLASS ltc_set_cell_value_types IMPLEMENTATION. ENDMETHOD. ENDCLASS. + + +CLASS ltc_is_date_format IMPLEMENTATION. + + METHOD setup. + TRY. + xlsx = NEW #( ). + cut = NEW #( xlsx ). + CATCH zcx_excel INTO DATA(excel_error). + cl_abap_unit_assert=>fail( + msg = 'Could not create instance' + quit = if_aunit_constants=>class ). + ENDTRY. + ENDMETHOD. + + METHOD teardown. + CLEAR cut. + CLEAR xlsx. + ENDMETHOD. + + METHOD date_format_iso. + cl_abap_unit_assert=>assert_true( + act = cut->is_date_format( ip_value = 'yyyy-mm-dd' ) + msg = 'ISO date format not reckognized' + level = if_aunit_constants=>critical ). + ENDMETHOD. + +ENDCLASS. + + +CLASS ltc_is_time_format IMPLEMENTATION. + + METHOD setup. + TRY. + xlsx = NEW #( ). + cut = NEW #( xlsx ). + CATCH zcx_excel INTO DATA(excel_error). + cl_abap_unit_assert=>fail( + msg = 'Could not create instance' + quit = if_aunit_constants=>class ). + ENDTRY. + ENDMETHOD. + + METHOD teardown. + CLEAR cut. + CLEAR xlsx. + ENDMETHOD. + + METHOD time_format_iso. + cl_abap_unit_assert=>assert_true( + act = cut->is_time_format( ip_value = 'hh:mm:ss' ) + msg = 'ISO time format not reckognized' + level = if_aunit_constants=>critical ). + ENDMETHOD. + +ENDCLASS. diff --git a/src/zcx_excel.clas.xml b/src/zcx_excel.clas.xml index 5bbc56e..23ddcb3 100644 --- a/src/zcx_excel.clas.xml +++ b/src/zcx_excel.clas.xml @@ -42,6 +42,14 @@ 0001 + + + LIMU + CPUB + ZCX_EXCEL + 0001 + + CONSTRUCTOR