Skip to Content

Often, a seemingly small and simple task is actually a little bigger, the more and deeper you think of it, you sure have experienced this as well. Here’s an example:

Task: Select something from the last 24 hours (e.g. sales orders (VBAK) maybe to see the turnover in $ in the last 24h hours, or how many units of Product X have been produced per plant )
So, we need to calculate the “target” date and time -> 24 hours in the past.

1.st approach:

data(lv_target_date) = sy-datum - 1. 
data(lv_target_time) = sy-uzeit.

simple.

But we probably want 24hours back from the users “definition of now“, not from the system-time (yeah, they might be different, if user and server are in different timezones), so better use sy-datlo + sy-timlo:

data(lv_target_date) = sy-datlo - 1. 
data(lv_target_time) = sy-timlo.

Now the following was not explicitly stated, but implicitly it is: If “today” is a Monday, we would want to look back as far as Friday (’cause there’s not turnover/production/… at the weekend – well, unless we have online sales, but that’s another story.)
So, things are clear: we have to take the factory calendar into account.

 

SELECT SINGLE fabkl FROM t001w INTO data(lv_factory_calendar)
WHERE werks = iv_werks.

*if there is no factory calendar, we're fine with what we already calculated!
CHECK sy-subrc EQ 0.
CHECK lv_factory_calendar IS NOT INITIAL.

CALL FUNCTION 'DATE_CONVERT_TO_FACTORYDATE'
EXPORTING
  correct_option = '-' 
  date = lv_target_date 
  factory_calendar_id = lv_factory_calendar 
IMPORTING
  date = lv_target_date 
* factorydate = 
* workingday_indicator = 
EXCEPTIONS
  calendar_buffer_not_loadable = 1
  correct_option_invalid = 2
  date_after_range = 3
  date_before_range = 4
  date_invalid = 5
  factory_calendar_not_found = 6
OTHERS = 7.

IF sy-subrc <> 0.
*doesn't matter.
ENDIF.

What does it do: if what we calculated as lv_target_day in the first step is NOT a working day, go as far back, until you hit a working day.

That’s what I wanted to say about the date part, but I also have one for the time:

If it’s March, 28th (this year, 2018), 8 O’clock in the morning, what’s the target if we go 24h back? Well: March, 27th, 7 O’clock in the morning! That’s right, at least if we are in a timezone which switched to daylight-saving-time: the hour between 2 and 3 didn’t exist that night!

I have no solution to that, I think this can or at least should not be coded by hand. I would hope + guess that there’s a library somewhere, which takes care of exactly those things I mentioned here. If you know it, please share in the comments.

Let me end with a nice blog I read a few years ago and now went searching for again:
http://infiniteundo.com/post/25326999628/falsehoods-programmers-believe-about-time

( If you like it, you probably like this one about names as well:
https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/ )

Also, to link to a currently trending topic: you probably can and should write #unit-tests for those edge-cases displayed here.

Over to you:
– Do you see other problems not yet addressed here?
– Do you know the library (e.g. ABAP-Class) that helps me with all the time-handling
– I guess there should be a more modern way (ABAP-Class) for “CALL FUNCTION ‘DATE_CONVERT_TO_FACTORYDATE'” , do you know it?
– would you have fallen for one of the ‘traps’ I mentioned here, where they all obvious to you?

This and other input is most welcome!

best
Joachim

To report this post you need to login first.

11 Comments

You must be Logged on to comment or reply to a post.

  1. Lars Breddemann

    That’s the kind of blog post I like! Well written, about a problem most developers encounter at some point. Co-incidentally my last post was also about dealing with time/date data so I can relate to this one.

    (0) 
  2. Sandra Rossi

    To subtract 24h from March, 28th (this year, 2018), 8 O’clock in the morning, you should convert the initial date/time from its time zone to UTC, subtract 24h, then convert back from UTC to the initial time zone. UTC never varies (except from time to time to adjust to the actual position of the Earth around the sun…)

     

    (3) 
  3. Suhas Saha

    Hi Joachim,

    Adding my 2 cents to what Sandra Rossi has already mentioned:

    1. I always use UTC to do the timestamp handling. I use local time-zones for display purposes only
    2. Use the system class CL_ABAP_TSTMP to do the timestamp calculations

    You can refer to the code snippet below to see my approach to your problem:

     

    CLASS lcl_main DEFINITION CREATE PUBLIC.
    
      PUBLIC SECTION.
    
        METHODS:
          constructor,
          start.
    
      PROTECTED SECTION.
      PRIVATE SECTION.
        DATA:
          mv_user_time        TYPE sytime,
          mv_user_date        TYPE sydatum,
          mv_user_tzone       TYPE tznzone VALUE 'CET',
          mv_time_shift       TYPE i VALUE '24',
          mv_factory_calender TYPE t001w-fabkl VALUE '01'.
    
        METHODS:
          adjust_against_factory_cal
            CHANGING
              cv_date TYPE d,
    
          shift_time
            EXPORTING
              ev_shifted_date TYPE d
              ev_shifted_time TYPE t.
    
    ENDCLASS.
    
    CLASS lcl_main IMPLEMENTATION.
    
      METHOD constructor.
    
        me->mv_user_date = sy-datlo.
        me->mv_user_time = sy-timlo.
    
        cl_demo_input=>new(
        )->add_field(
          EXPORTING
            text        = |User Date(YYYYMMDD)|
          CHANGING
            field       = mv_user_date
        )->add_field(
          EXPORTING
            text        = |User Time(HHMMSS)|
          CHANGING
            field       = mv_user_time
        )->add_field(
          EXPORTING
            text        = |User Time-Zone|
          CHANGING
            field       = mv_user_tzone
        )->add_field(
          EXPORTING
            text        = |Time Shift(in Hours)|
          CHANGING
            field       = mv_time_shift
        )->add_field(
          EXPORTING
            text        = |Factory Calender|
          CHANGING
            field       = mv_factory_calender
        )->request(
        ).
    
      ENDMETHOD.
    
      METHOD shift_time.
    
        CONVERT DATE me->mv_user_date TIME me->mv_user_time
                INTO TIME STAMP DATA(lv_user_tstmp) TIME ZONE me->mv_user_tzone.
        TRY.
    
            DATA(lv_time_shift_in_secs) = 3600 * me->mv_time_shift.
    
            CONVERT TIME STAMP cl_abap_tstmp=>subtractsecs(
                                tstmp = lv_user_tstmp
                                secs  = lv_time_shift_in_secs )
              TIME ZONE me->mv_user_tzone
              INTO DATE ev_shifted_date TIME ev_shifted_time.
    
            me->adjust_against_factory_cal(
              CHANGING
                cv_date = ev_shifted_date
            ).
    
          CATCH cx_parameter_invalid_range cx_parameter_invalid_type
            INTO DATA(lx_tstmp_conv_error).    "
            MESSAGE lx_tstmp_conv_error TYPE 'I'.
        ENDTRY.
      ENDMETHOD.
    
    
      METHOD adjust_against_factory_cal.
        DATA: lv_adjusted_date TYPE scal-date.
    
        CALL FUNCTION 'DATE_CONVERT_TO_FACTORYDATE'
          EXPORTING
            correct_option               = '-'    " Flag how workday should be calculated
            date                         = cv_date    " Date to be converted into factory calendar date
            factory_calendar_id          = me->mv_factory_calender    " Factory calendar ID
          IMPORTING
            date                         = lv_adjusted_date " Date to be converted into factory calendar date
          EXCEPTIONS
            calendar_buffer_not_loadable = 1
            correct_option_invalid       = 2
            date_after_range             = 3
            date_before_range            = 4
            date_invalid                 = 5
            factory_calendar_not_found   = 6
            OTHERS                       = 7.
        IF sy-subrc = 0.
          cv_date = lv_adjusted_date.
        ENDIF.
    
      ENDMETHOD.
    
      METHOD start.
        me->shift_time(
          IMPORTING
            ev_shifted_date = DATA(lv_shifted_date)
            ev_shifted_time = DATA(lv_shifted_time)
        ).
    
        cl_demo_output=>new(
        )->begin_section( |User Date/Time|
        )->write_text( |{ me->mv_user_date DATE = USER }/{ me->mv_user_time TIME = USER }|
        )->end_section(
        )->begin_section( |Shifted Date/Time|
        )->write_text( |{ lv_shifted_date DATE = USER }/{ lv_shifted_time TIME = USER }|
        )->end_section(
        )->display(
        ).
    
      ENDMETHOD.
    
    ENDCLASS.
    
    START-OF-SELECTION.
      NEW lcl_main( )->start( ).

     

    BR,

    Suhas

    (2) 
  4. Shai Sinai

    One point I didn’t follow:

    Why would you like to use user time (sy-timlo) instead of system time (sy-uzeit)?

    As far as I know, unless code is written wrongly, all DB values of dates/times should be written in system time. Hence, selection by user time would lead to incorrect reults.

    (1) 
    1. Suhas Saha

      A valid point!

      As far as I know, unless code is written wrongly, all DB values of dates/times should be written in system time.

      If i understand correctly the data correctness depends on the discipline of the developer.

      Sometimes i wonder why isn’t “time” (timestamp, as well) handled as currency/quantity field in ABAP? IMHO 09:00 alone without the timezone isn’t a complete representation of time! After all time is relative 😉

      Any thoughts?

      (0) 
      1. Shai Sinai

        Well, at least in case of timestamps, time zone is quite redundant because value should always be in UTC (again, unless code is bad).

         

        (0) 
  5. Joachim Rees Post author

    Thanks Sandra Rossi and Suhas Saha for the general idea and the implementation example with timestamps.
    Very clever, I’ll add this to my coding best practices!

    So far, no one has mentioned an alternative for

    CALL FUNCTION 'DATE_CONVERT_TO_FACTORYDATE'

    and I also see Suhas using it in the example, so it’s probably the best we currently have, right?!

     

    best

    Joachim

    (0) 

Leave a Reply