Skip to Content
Author's profile photo Joachim Rees

Simple(?) task: look at the past 24 hours

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

Assigned Tags

      11 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Lars Breddemann
      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.

      Author's profile photo Joachim Rees
      Joachim Rees
      Blog Post Author

      Thanks for your feedback, Lars!
      I'll take the opportunity to have a look at the blog you mentioned. 🙂

       

       

       

      Author's profile photo Sandra Rossi
      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...)

       

      Author's profile photo Suhas Saha
      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

      Author's profile photo Sandra Rossi
      Sandra Rossi

      Thank you Suhas. Yep, I forgot to mention CL_ABAP_TSTMP 😉

      Author's profile photo Shai Sinai
      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.

      Author's profile photo Suhas Saha
      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?

      Author's profile photo Shai Sinai
      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).

       

      Author's profile photo Joachim Rees
      Joachim Rees
      Blog 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

      Author's profile photo Shai Sinai
      Shai Sinai

      Well, DATE_CONVERT_TO_FACTORYDATE will do the work, but you can also use FM END_TIME_DETERMINE.

      Author's profile photo Sandra Rossi
      Sandra Rossi

      No modern way, AFAIK