Skip to Content
Author's profile photo Thomas Nitschke

Date and Time in SAP Gateway Foundation

In a first blog post regarding conversions in SAP Gateway Foundation we discussed their relevance in the context of the differences between the ABAP type system and the OData type system. Although not directly related to conversions the handling of date and time perfectly fits into this topic.

ABAP knows date, time, and different representations of time stamps. And, it provides reuse functions to execute date and time calculations. OData has its own date and time definitions with functions that may be used in $filter expressions, for example.

Often, questions arise how the different representations map and how a data provider needs to be implemented to create a best match between both worlds. It is certainly not possible to dig into each and every detail. But, let us start with a few aspects.

Date and Time in OData

To represent date and time information, the OData specification in version 2.0 knows the three primitive types

  • Edm.DateTime,
  • Edm.Time, and
  • Edm.DateTimeOffset.

Since this is not really sufficient for business applications specification version 4.0 switches to

  • Edm.Date,
  • Edm.TimeOfDay,
  • Edm.DateTimeOffset, and
  • Edm.Duration.

We focus on specification version 2.0 and restrict the discussion to Edm.DateTime and Edm.DateTimeOffset. Details regarding the representations can be found in the OData specification, the ISO 8601 standard, and in http://www.w3.org/TR/xmlschema-2. Here, we quickly list the different formats. The literals are used in the URI, that is, in $filter expressions or key predicates, for example. ATOM and JSON refer to the content type of the request or response payload. The number of decimal places available for sub-seconds is defined by the facet precision.

Edm.DateTime represents a date and a time in UTC (formerly, Greenwich Mean Time):

Representation Example
Literal datetime’yyyy-mm-ddThh:mm[:ss[.fffffff]]’ datetime’2016-07-08T12:34:56′
ATOM yyyy-mm-ddThh:mm[:ss[.fffffff]] 2016-07-08T12:34:56
JSON “\/Date(<ticks>)\/”<ticks> = number of milliseconds since midnight Jan 1, 1970 “\/Date(1467981296000)\/”

The ticks in the JSON representation may also be negative to describe dates and time before Jan 1, 1970. “\/Date(-6847804800000)\/” is midnight Jan 1, 1753, for example.

Edm.DateTimeOffset adds time zone information relative to UTC. The date and time information is amended by the standard time difference (offset) with the sign v: +01:00 for Central European Time (CET) or -05:00 for Eastern Standard Time (EST), for example.

Representation Example
Literal datetimeoffset’yyyy-mm-ddThh:mm:ss[.fffffff]Z|vii:nn’ datetimeoffset’1970-01-01T00:00:01+01:00′
ATOM yyyy-mm-ddThh:mm:ss[.fffffff]Z|vii:nn 1970-01-01T00:00:01+01:00
JSON “\/Date(<ticks>[“+” | “-” <offset>)\/”<ticks> = number of milliseconds since midnight Jan 1, 1970<offset> = number of minutes to add or subtract “\/Date(1000+0060)\/”

The character Z at the end of the literal or ATOM representation refers to UTC. Hence, datetimeoffset’2016-07-08T12:34:56Z’ and datetime’2016-07-08T12:34:56′ are equivalent.

Date and Time in ABAP

ABAP provides two predefined data types to handle dates (TYPE D) and times (TYPE T). Additionally, there is a data element SYST_TZONE (an integer) to describe a time zone as the time difference to UTC in seconds. Hence, date and time information is being split up into several fields.

But there are also options to handle time stamps in single fields and to use those fields for time stamp calculations (see ABAP class CL_ABAP_TIMESTAMP_UTIL, for example). The following data elements represent timestamps. While they use different underlying data types they are all constructed as a concatenation of year, month, day, and time of day.

Data Element Data Type ABAP Type Example
TIMESTAMP[1] DEC 15 P LENGTH 8 20160708123456
TIMESTAMPL[2] DEC 21,7 P LENGTH 11 DECIMALS 7 20160708123456.1234567
TZNTSTMPSL NUMC 15 N LENGTH 15 020160708123456
TZNTSTMPLL NUMC 21 N LENGTH 21 201607081234561234567

Still, any time zone information needs to be kept separately.

Mapping

This separation is the main issue when trying to translate date and time information between ABAP and OData. In general, the OData Library in SAP Gateway Foundation is only able to serialize one ABAP field into one OData primitive property and to de-serialize a property into one ABAP field. Separate timestamp and time zone information can neither be combined into a single Edm.DateTimeOffset property nor can such a property be split up into several ABAP fields.

As a result, any content of a property of type Edm.DateTimeOffset in an OData request payload or in the request URI is converted and provided to the data provider of the service in UTC. The time zone information gets lost. An OData response created by SAP Gateway Foundation may only expose date and time content in UTC. Time zone information is not added in the OData response since it is not clear which time zone is supposed to be used.

The usage of conversion exits (see ‘Conversions in SAP Gateway Foundation – Part 1‘) to split Edm.DateTimeOffset information or combine ABAP fields is not possible. SAP Gateway Foundation does not provide any means to model such relationships.

Hence, time zone information can only be handled by the data provider of the service. It is not possible to expose it correctly as part of an Edm.DateTimeOffset property but only as a separate property of type Edm.Int32, for example. The client implementation would need to implement the translation from and into a desired representation.

Therefore, we can stick to Edm.DateTime when summarizing the mapping in a small example. The following screenshots show a sample piece of implementation in /IWBEP/IF_MGW_APPL_SRV_RUNTIME~GET_ENTITYSET:

DATA: 
  BEGIN OF ls_entity,
        key      TYPE int8,
        dt_date  TYPE d,
        dt_dts   TYPE timestamp,
        dt_nts   TYPE tzntstmpsl,
        dt_dtsl  TYPE timestampl,
   END OF ls_entity.

   ls_entity-key = 2.

   " Date
   " TYPE D
   ls_entity-dt_date = '17530101'.                "YYYYMMDD

   " short timestamp in decimal (DEC 15) and numerical (NUMC 15) format
   " TYPE TIMESTAMP
   ls_entity-dt_dts = 20160708123456.             "YYYYMMDDhhmmss
   " TYPE TZNTSTMPSL
   ls_entity-dt_nts = 20160708123456.             "YYYYMMDDhhmmss

   " long timstamp in decimal (DEC 21,7) format
   " TYPE TIMESTAMPL
   ls_entity-dt_dtsl = '20160708123456.1234567'.  "YYYYMMDDhhmmss.fffffff
   " TYPE TZNTSTMPLL
   " cannot be used

The corresponding ATOM response would be:

And, a JSON response looks like:

Please observe that SAP Gateway Foundation is not able to handle ABAP fields of type TZNTSTMPLL. It can only translate numerical timestamp information of length 15 providing the information to the OData Library as packed number of length 8. If you need to use sub-seconds please choose the decimal representation DEC 21, 7 (data element TIMESTAMPL) in your service implementation.

Dates are exposed with time 00:00:00. And, if the number of decimals defined by the precision of Edm.DateTime or Edm.DateTimeOffset is lower than the internal number of decimals for sub-seconds the remainder is cut off – no rounding takes place. Similarly, trailing zeros are added to the ticks in the JSON representation if required and micro-/nano-seconds are cut off.

Specialties

OData version 2.0 initially specified Jan 1, 1753 as the lower boundary for dates to avoid ambiguities with the Julian Calendar while version 4.0 just refers to the Proleptic Gregorian Calendar (including projected dates before Jan 1, 1583) but also allows for year 0 and negative years. In any case, the initial values of the ABAP data types do not have a corresponding representation in Edm.DateTime.

Therefore, the OData library in SAP Gateway Foundation serializes an initial ABAP date / time value to NULL in the OData response if the corresponding property is nullable. If the property is not nullable an exception is raised that results in an error response.

Additionally, SAP Gateway Foundation does not take any potential lower boundary into account but just translates the ABAP value. Decimal value 9121123000000 would result in an ATOM representation 0912-11-23T00:00:00 – the birthday of Otto I.

We have seen that the JSON representation works with milliseconds relative to midnight Jan 1, 1970. The type is a JSON string. In such a JSON string, SAP Gateway Foundation also accepts the ATOM format of Edm.DateTime or Edm.DateTimeOffset in request payloads.

None of the OData date and time representations includes a daylight savings time indicator. Times between 2:00 and 3:00 in the night when daylight savings time ends cannot be expressed without ambiguity.

Let us finish with a remark on conversion exits. With the former SAP GUI is was sometimes necessary to format timestamps into a kind of external representation. Conversion exit TIMES does such a conversion and might be attached to a timestamp domain in the ABAP Dictionary. Not only does this exit convert the timestamp with system time zone SY-ZONLO it also creates a character-like representation using the WRITE statement (please refer to the ABAP documentation). Such a representation can neither be handled by SAP Gateway Foundation nor does it make sense to work with such a representation that has nothing to do with the OData format. Therefore, please make sure not to use domains with those conversion exits or to switch off the conversion as explained in ‘Conversions in SAP Gateway Foundation – Part 1‘.

 

[1] Or TZNTSTMPS

[2] Or TZNTSTMPL

Assigned Tags

      46 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Andre Fischer
      Andre Fischer

      I like history and I especially like the example of using the birthday of Otto I. ??

      Author's profile photo KETHAN UPPALAPATI
      KETHAN UPPALAPATI

      Hi Thomas,

       

      Is there a way we can convert JSON timestamp into SAP?

      Currently in my webservice response I am getting date as

      /DATE(1509970085096-0600)/

      I have to convert it to YYYYMMDD ABAP format.

      Thank you.

      Kethan.

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hello Kethan,

      I am not aware of a re-use function that would do the job. So, I guess you need to write your own piece of ABAP.

      Use

        FIND REGEX '^\\?/Date\((-?[[:digit:]]+)([+-][[:digit:]]{1,4})?\)\\?/$'
            IN lv_value SUBMATCHES lv_ticks lv_offset.

      to extract the ticks and the offset from the JSON representation. Then calculate the seconds from lv_ticks considering lv_offset, divide by 86400 as an integer (using DIV) and add this to 19700101 to get the date.

      Thomas

      Author's profile photo Ioan Radulescu
      Ioan Radulescu

      hey Kethan, I don't convert at this low level, using sap gateway but try to use the statement CONVERT ... that's what I use

      Author's profile photo Manjunath Mulimani
      Manjunath Mulimani

      Try /IWCOR/CL_DS_EDM_DATE_TIME=>PARSE_JSON_DATETIME

      Author's profile photo Ned Falk
      Ned Falk

      In SAC feed via odata i have the output field from a CDS view   "posting date".   It shows as a AA type "character field not a "date" field .  I am not an abaper,  but I can navigate CDS views.  I think the field started as a date field in the abap table .. but where how do i tell an abaper to fix the code to make it a date field so I can use it for incremental loading.

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      It should be checked how the corresponding field is typed in the CDS View that is the basis for the OData exposure. Typing may change when views are stacked on top of the original database table. Then, the question is how the OData service was created: manual implementation on top of the CDS View, auto-exposure?

      Author's profile photo Ned Falk
      Ned Falk

      THanks so much..  for responding ..  just to keep it clean the view in question is the SAP delivered C_RevenueFromInvoiceQry  view.   It has various anotations that should drive odata to make a date field ..   Like @Semantics.systemDate.createdAt: true   and the tables have dats fields..

      The service is generated upon activation of the CDS view with  ODATA publish true annotation.    I have heard rumors of another way but I am resisting that because I don't know it!!! and itseems dumb that an annotation does not exist that would drive the odata to have a date field..  🙂

      Author's profile photo Ned Falk
      Ned Falk

      If I have to do something different to get the date field exposed.. can you point me as if I was wearing very thick glasses to how to do it set by step..  it would be very appreciated.     SAC documentation should mention this ..  it must be driving hundreds of people nuts!

       

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hello Ned. I am afraid I cannot help with that specific aspect. I can see that the OData service is an auto exposure of the mentioned CDS View and indeed provides the billing date as Edm.String. I suppose this is intended - OData V2 does not know a proper date type, it just uses Edm.DateTime which might not be suitable in this case. I am neither an expert in the analytics space nor in that specific area.

      Maybe it is a good idea to post the topic in the 'Answers' section as a question where it could get appropriate attention. Sorry. Thomas

      Author's profile photo Ned Falk
      Ned Falk

      Again thanks for reply..  can you lead me to where you can manually alter the CDS generated service and override the edm.string  to edm.datetime?

       

      best Ned

      Author's profile photo Andre Fischer
      Andre Fischer

      You have to build an OData Service using the Referenced Data Source appraoch.

      Here you can override the define( ) method in the MPC_EXT class as described here for another used case.

       

      METHOD define.
        DATA: lo_entity_type TYPE REF TO /iwbep/if_mgw_odata_entity_typ, 
      
              lo_property  TYPE REF TO /iwbep/if_mgw_odata_property.
        super->define( ).
        "Create sap:creatable="true" annotation for product entity set  
      
      lo_entity_type = model->get_entity_type( zcl_z_be2ui_###_mpc=>gc_zc_be2ui_product_###type ).
        IF lo_entity_type IS NOT INITIAL.  
      
      lo_entity_type->set_creatable( abap_true ).  
      
      "Create sap:creatable="false" annotation for the key field "Product"  
      
      lo_property = lo_entity_type->get_property( 'Product').  
      
      IF lo_property IS NOT INITIAL.  
      
      lo_property->set_creatable( abap_false ).  
      
      ENDIF.  
      
      ENDIF.
        ENDMETHOD.
      
      or as described in this blog.
      https://blogs.sap.com/2017/04/21/how-to-add-annotations-to-an-odata-service-using-code-based-implementation/
      
      
      Author's profile photo Ned Falk
      Ned Falk

      thanks for the reply..  I can't tell you how long I have been trying to find this out.!!Seems crazy that SAC would highlight this feature for incremental loading yet not mention any links to this.   Are you 100% sure this is the only way... Why do decimals output as the correct EDM type by not dates?   Why is there not a cds anotation that does this by now  🙁  ..

      Now that I have vented.. I am not really a coder..  but having worked for SAP for 23 yrs before retiring ) I am a good hacker.

      It seems that the great blogs you wrote might be dated? but I ma very nervous to procede 🙂

      1) So I go to segw then create a project with the option "service model for ref service"
      2) choose the original service then it look like I can use this screen to just force a change to the entity type...


      Does this seem like the correct thing to do..  ( venting again  - why is is so hard:)

      Author's profile photo Ned Falk
      Ned Falk

      It seems to stop on me... when I get to the screen to to select the ref data source wizard  the error it gives me is "CDS view XXXXXx is intended for analytics and prevents me form using it as a ref data source.

       

      ANy more docs you can porvide??

       

      Author's profile photo Kjetil Kilhavn
      Kjetil Kilhavn

      I am currently working on a OData v2 service implementation where a Function Import has (among else) one input parameter with data type Edm.DateTimeOffset

      It seems SAP Gateway can't handle positive UTC offsets for the datetimeoffset'...' function.

      datetimeoffset'2018-01-02T09:15:00Z' arrives as 20180102091500, as expected.
      datetimeoffset'2018-01-02T09:15:00-06:00' to represent the same local time in Houston (Texas, USA) arrives as 20180102151500, which is also as expected.
      datetimeoffset'2018-01-02T09:15:00+01:00' to represent the same local time in Stavanger (Norway) arrives as 20180102091500, which is not at all as expected.

      As far as I have understood the documentation I have found positive and negative offsets up to 14:00 [-14:00, 14:00] is what the standard defines. SAP Gateway seems to only handle negative offsets, but without input validation, so datetimeoffset'2018-01-02T09:15:00-24:00' arrives as 20180103091500 and even datetimeoffset'2018-01-02T09:15:00-99:00' is accepted and arrives as 20180106121500.

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hi Kjetil... sorry for the late reply. I didn't have the chance to check that but I would propose to open an incident with respect to this issue. Could be a bug. Thomas

      Author's profile photo Kjetil Kilhavn
      Kjetil Kilhavn

      I would if I could... Unfortunately I am not allowed to, because that requires that my S-user is "linked to" a SAP installation. It is not. However, I can prepare the case and ask if one of the employees here will do it.

      Author's profile photo Ioan Radulescu
      Ioan Radulescu

      ok thanks Thomas, I have a question since I can't seem to make it work in odata 2 with sap GW. How do I write a filter that takes a timestamp? I'm trying to select all bookings from a certain date to the hi-date - and I'm testing it in the sap gateway client:

      Bookings?$filter=CreateDate eq '2018-01-17T23:00:00' - invalid token in position 14

      And sometimes it works when I just do this, but sometimes it doesn't:

      Bookings?$filter=CreateDate eq DateTime'2018-01-17T23:00:00'

      I can swear just a few minutes ago I was getting the error invalid toke in position 22 for the statement above... could it have something to do that I was copying it from an email text, I wonder - because it was working yesterday evening. The only difference is that I deleted the DateTime part before and wrote it by hand.

       

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hi Ioan,

      your second URL snippet comes close. Date and time in $filter require the URI literal form - see http://www.odata.org/documentation/odata-version-2-0/overview/#AbstractTypeSystem.

      $filter=CreateDate eq datetime'2000-12-12T12:00'

      should be fine.

      Thomas

      Author's profile photo Ioan Radulescu
      Ioan Radulescu

      Thanks! It all works now. Just as a hint to others I used the statement CONVERT DATE... to generate the timestamps too if needed...

       

      I tried to "like" your comment but it doesn't work.

      Author's profile photo PAPPIREDDY SANJEEVA REDDY
      PAPPIREDDY SANJEEVA REDDY

      hi  KETHAN UPPALAPATI  ,

      am telling the scenario that i followed .

      i make the data type of that date field from DATS to CHAR8 .

      in this case date will come like 20181904.

      for the frontend team it is easy to convert the above date field according to their requirement.

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Thanks, Kethan Uppalati. Of course, it is possible to send plain data in a certain format to the client and let the formatting happen on client-side.

      But at the end, the OData service will only expose strings somehow foiling the elaborate type system of the OData standard.

      Author's profile photo Ned Falk
      Ned Falk

      I want the opposite,  as others have posted I don't understand why the  CDS view accessing a DATS ABAP field outputs EDM.STRING when the odata service is executed and the meta data is reviewed.

      When the CDS view is used to send data to SAC there is a feature that relies on DATE time fields to do an incremental load.. this is critical ..

      How can this be set correctly ?  is there a semantics tag?

      Author's profile photo kyo choi
      kyo choi

      Not sure what the issue is with date.  I’ve just passed Datetime field and in the debug mode, it shows that time portion has been auto-magically filtered out.  BTW, my Netweaver stack is 7.51 SP2.

      SAP Gateway Client with OData based out of CDS View with Association.

      /sap/opu/odata/SAP/ZCDSVA_SCARR_SRV/FLIGHTSet(Carrid=’AA’,Connid=’0017′,Fldate=datetime’2018-03-15T00%3A00%3A00′)

      And debug mode of  method FLIGHTSET_GET_ENTITY.

      Filter working fine,

      Filter in debug mode,

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hello Kyo Choi,

      It is a bit difficult to analyze from a distance. But I assume you exposed a CDS view containing an attribute type as date (ABAP built-in type D).

      The blog describes that OData V4 introduces Edm.Date but OData V2 does not provide an appropriate data type for dates. Hence, the Gateway Foundation needs to map the ABAP date to Edm.DateTime. The metadata of the service shows Fldate as Edm.DateTime and both the key as well as the filter string need to an appropriate literal representation.

      But the Gateway Foundation framework knows that the internal representation had been an ABAP date and automatically maps accordingly. The service provider already gets the internal representation. This is the magic you are referring to. And, the mapping simply cuts of the time information.

      Thomas

      Author's profile photo Wouter Peeters
      Wouter Peeters

      Very good blog, thanks Thomas! I have one question: is it the idea that you perform the value conversion to UTC in ABAP code, and the OData only does the displaying in Epoch?

      When we have a Timestamp in ABAP for our timezone, OData displays it in epoch but the value is our timezone value, not the adjusted value to UTC. I would then have thought that SAP always does the UTC value (timezone) conversion in out and out in.

      Thanks

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hello Wouter,

      Thanks a lot. Sorry for the late reply. If I understand the question correctly: no, you do not need to convert in ABAP.

      If the client sends an OData request that specifies a timezone ≠ UTC then the OData library in the SAP Gateway Foundation framework will convert to UTC. The data provider class you would implement already gets the converted date/time in the respective ABAP format.

      If you return date/time data from your data provider class to the framework then this date/time will always be interpreted as if it would be UTC - without any conversion. Even if you stored another timezone somewhere in your database table you will not be able to provide this information to the Gateway framework.

      But: if you are able to handle time zones somehow because you store them separately and a proper handling at the client side is available then you would need to do conversions in your own ABAP code.

      Thomas

      Author's profile photo YASWANTH NAKKINA
      YASWANTH NAKKINA

      Hi Thomas,

      I kept data type as dats in my database table while creating (post method) a new record in OData. It was created when using xml format but not in json format.

      Is there any way we can convert JSON timestamp into SAP?

      Currently in my webservice response I am getting date as

      /DATE(1509970085096-0600)/

      I have to convert it to YYYYMMDDHHSS ABAP format.

       

      Thanks.

      Yaswanth Nakkina

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hello Yaswanth,

      In which context do you want to covert this information? If you specify a json payload for an OData service that is implemented with SAP Gateway Foundation, then the OData library and the Gateway framework automatically convert the data. You receive the information in the data provider class already in an ABAP format depending on the definition of your OData service.

      Thomas

      Author's profile photo Nikolay Bystritskiy
      Nikolay Bystritskiy

      Hello Thomas,

      I receive timestamp from backend in long format (ABAP Type TIMESTAMPL).

      Calling the method getMilliseconds of Edm.DateTimeOffset I make sure that correct value of milliseconds is received.

      Then I want to send the very same timestamp value to the server via ODataModel.update (v2) method. My problem is that milliseconds part is being cut from the value. Example:

      expected value 20191126130221.5254400

      got on server 20191126130221.5250000

       

      Would you have a suggestion how can I fix it?

       

      Component: SAP_GWFND 751 0007 SAP Gateway Foundation 7.40

      Ping Thomas Nitschke

       

      Kind regards

      Nikolay

       

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hello Nikolay,

      I am not sure what type of OData client you are using. For OData V2, the situation is as follows:

      • If the client sends a JSON payload then only milliseconds can be handled (no smaller units) because the JSON representation is defined (!) as milliseconds in the unix age – see description in the blog post. Your example value is represented by  “\/Date(1574773341525+0000)\/” … you can guess the 525 milliseconds. 
      • If the client would use ATOM XML then the OData library in SAP Gateway Foundation is able to parse 2019-11-26T13:02:21.5254400Z into the expected backend representation with the 4.4 microseconds.

      The blog post also describes a special feature of the OData library in SAP Gateway Foundation. You can transfer an ATOM XML representation of DateTimeOffset (2019-11-26T13:02:21.5254400Z) in the value of the DateTimeOffset property in a JSON payload. Then, the result is also as expected. But this is a specialty that would need to be supported by the client.

      Regards,

      Thomas

      Author's profile photo Nikolay Bystritskiy
      Nikolay Bystritskiy

      Hello Thomas,

      Thank you for quick response. I have mistaken nanoseconds for milliseconds. As workaround, I will round nanoseconds on backend, as we do not really need such precision.

      Kind regards

      Nikolay

      Author's profile photo Jakub Filak
      Jakub Filak

      It is quite interesting to use 7 digits for microseconds especially because python's datetime.strptime [0] format fails to parse Edm.DateTime values with formatting string "%Y-%m-%dT%H:%M:%S.%f". The code "%f" accepts 1-6 digits and the 7th digit is reported as an leftover.

      I have to come up with a fix for our PyOdata [1].

      What are your suggestions?

      Can we just ignore those hundreds of nanosecond?

       

       

      0: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes

      1: https://github.com/SAP/python-pyodata/issues/83

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hi Jakub,

      That's an interesting topic. I am not an expert in all the specifications but it seems that both ISO 8601 as well as RFC 3339 allow for any number of digits as fractional seconds. The specifications point out that the communication partners must agree upon the usage of fractional seconds.

      As far as I can see in https://docs.oasis-open.org/odata/odata/v4.01/cs01/abnf/odata-abnf-construction-rules.txt, OData restricts fractional seconds to 12 (!) digits. The 7 digits rather seem to be a restriction imposed by the ABAP data type - might need to correct the blog post a little.

      Ignoring extra digits is probably an option. Will try to get some more background information but can't promise.

      Thomas

       

      Author's profile photo Roei Sagi
      Roei Sagi

      Greate blog, really helpful!

      I'm experiencing some issues though... When trying to create a POST test data for timestamps (DEC15) inside the gateway client.

      No matter how I format the the dates it will always give me either cx_parameter_invalid_range with the details of "Property 'X' at offset 'Y' has invalid value 'Z'".

      Is there a way to pass that data when doing testing through the gateway client?

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hello Roei,

      Thanks a lot. And, sorry for the late reply.

      The exception points to the ABAP OData Library. So, I suppose there is an issue with the data format you use in the payload of your POST request.

      TIMESTAMPS should understand date + time up to seconds. Hence, an OData representation in XML would like 2020-06-09T08:13:00 and in JSON \/Date(1591683205)\/

      Please check the format of you request payload (XML or JSON) and try with examples above. If I does not work, someone would need to look into details in your system.

      Thomas

      Author's profile photo Bojan Ivanovic
      Bojan Ivanovic

      Hello Thomas Nitschke

      I am facing a bit of a problem with mapping edm.datetime.

      But the thing is I can't edit anything in ABAP Type editor. For some strange reason I cant switch it from Display to Edit mode (So I cant simply change Type kind to PACKED.

      I know I can do this in Odata v4 but I want to resolve this problem in v2.

      I know this is an old post but I'm hoping for a quick reply. Thanks in advance.

      Author's profile photo Andre Fischer
      Andre Fischer

      Hi Bojan,

      you can do the settings in the DEFINE method of your MPC_EXT class or the entity type specific define method.

      Kind regards,

      Andre

      super->define( ).
      lo_entity_type = model->get_entity_type( iv_entity_name = <SOURCE_ENTITY>). 
      lo_property = lo_entity_type->get_property( iv_property_name = <SOURCE_PROPERTY>). 
      lo_property->set_type_edm_datetime( ). 
      Author's profile photo Bojan Ivanovic
      Bojan Ivanovic

      Thank you Andre Fischer for a quick reply,
      thing is I am fairly new to SAP let me check if I understood this correctly.

      I should past this code that you sent in here:

      or should I actually redefine it bc it clearly says never to modify this class.

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hi Bojan,

      This is a 'warning' in SEGW. You need to check what you want to achieve with the service and how the model in ABAP looks like.

      If you have and want a date in your ABAP structure typed with the ABAP-internal type 'd', then the only possibility in OData V2 is a mapping to Edm.DateTime. But since this Edm type is able to transfer more information than just a date, the SEGW issues this 'warning'. It can be ignored in this case.

      It is a different story if you want to handle the complete date & time information in the backend - not just the date part. Then, a proper ABAP type must be chosen.

      The actual steps (see comment from Andre Fischer) then depend a bit on the service and the way it was created: manual, mapped data source, referenced data source... This goes a bit too far in the comments.

      Thomas

      Author's profile photo venkatesh AMARANENI
      venkatesh AMARANENI

      hi @thomas Nitschke,

      • I am developing Odata gateway services without SEGW t-code that means purely based on MPC and DPC classes no MPC ,DPC ext's
      • My doubt is I had date field (ersda)  as the key field I need to give it in URL-like range how can as I told I creating purely classes using abstract classes for both MPC and DPC classes
      • so is there anything I need to do in MPC or DPC classes? for that date field, I am using in expand entity set.
           WHEN 'maraHeaderCollection'.  "expand entity set using range 
        
                TYPES:BEGIN OF ty_maraheader,
                        matnr TYPE  matnr,
                        ersda TYPE ersda,
                        ernam TYPE  ernam,
                        laeda	TYPE laeda,
                        aenam	TYPE aenam,
                        vpsta	TYPE vpsta,
                        pstat	TYPE pstat_d,
                        lvorm	TYPE lvoma,
                        mtart TYPE  mtart,
                        mbrsh	TYPE mbrsh,
                        matkl	TYPE matkl,
                        bismt	TYPE bismt,
                        meins	TYPE meins,
                        bstme TYPE 	bstme,
                        zeinr TYPE 	dzeinr,
                        zeiar	TYPE dzeiar,
                        zeivr	TYPE dzeivr,
                        zeifo	TYPE dzeifo,
                        aeszn	TYPE aeszn,
                        blatt	TYPE blatt,
                        blanz	TYPE blanz,
                        ferth	TYPE ferth,
                        formt	TYPE formt,
                        groes	TYPE groes,
                        wrkst	TYPE wrkst,
                        normt TYPE  normt,
                        labor TYPE labor,
                        ekwsl	TYPE ekwsl,
                        brgew TYPE 	brgew,
                        ntgew	TYPE ntgew,
                        gewei	TYPE gewei,
                      END OF ty_maraheader.
        
                TYPES:BEGIN OF ty_maratomarc,
                        matnr TYPE  matnr,
                        werks TYPE  werks_d,
                        pstat TYPE 	pstat_d,
                        lvorm	TYPE lvowk,
                        bwtty TYPE  bwtty_d,
                        xchar	TYPE xchar,
                        mmsta TYPE 	mmsta,
                        mmstd	TYPE mmstd,
                        maabc	TYPE maabc,
                        kzkri	TYPE kzkri,
                      END OF ty_maratomarc.
        
                TYPES:BEGIN OF ty_maratomard,
                        matnr TYPE  matnr,
                        werks TYPE  werks_d,
                        pstat	TYPE pstat_d,
                        lvorm	TYPE lvolg,
                        lfgja	TYPE lfgja,
                        lfmon	TYPE lfmon,
                        sperr TYPE 	sperr,
                        labst TYPE labst,
                        umlme	TYPE umlmd,
                        insme	TYPE insme,
                        einme	TYPE einme,
                        speme	TYPE speme,
                        retme TYPE  retme,
                        vmlab	TYPE vmlab,
                      END OF ty_maratomard.
        
                DATA:maraheader      TYPE STANDARD TABLE OF ty_maraheader WITH DEFAULT KEY,
                     maratomarc      TYPE STANDARD TABLE OF ty_maratomarc WITH DEFAULT KEY,
                     maratomarc_temp TYPE STANDARD TABLE OF ty_maratomarc WITH DEFAULT KEY,
                     maratomard      TYPE STANDARD TABLE OF ty_maratomard WITH DEFAULT KEY,
                     maratomard_temp TYPE STANDARD TABLE OF ty_maratomard WITH DEFAULT KEY.
                DATA:    ls_mara TYPE  ty_maraheader,
                         ls_marc TYPE  ty_maratomarc,
                         ls_mard TYPE  ty_maratomard.
                TYPES:BEGIN OF ty_deepentity.
        *                maraheader TYPE STANDARD TABLE OF ty_maraheader WITH DEFAULT KEY,
                        INCLUDE TYPE ty_maraheader.
                TYPES:
                  maratomarc TYPE STANDARD TABLE OF ty_maratomarc WITH DEFAULT KEY,
                  maratomard TYPE STANDARD TABLE OF ty_maratomard WITH DEFAULT KEY,
                  END OF ty_deepentity.
        
                DATA:ls_deepentity TYPE ty_deepentity,
                     lt_deepentity TYPE STANDARD TABLE OF ty_deepentity.
        
                FIELD-SYMBOLS : <fs_ls_deepentity> LIKE ls_deepentity.
        
                DATA:ls_select_options TYPE /iwbep/s_cod_select_option,
                     ls_filter         TYPE /iwbep/s_mgw_select_option.
                DATA:lr_matnr TYPE RANGE OF matnr,
                     ls_matnr LIKE LINE OF lr_matnr.
        
                READ TABLE it_filter_select_options INTO ls_filter WITH KEY property = 'Matnr'.
        
                IF sy-subrc EQ 0.
                  LOOP AT ls_filter-select_options INTO ls_select_options.
                    ls_matnr-sign =    ls_select_options-sign.
                    ls_matnr-option =  ls_select_options-option.
                    ls_matnr-low =     ls_select_options-low.
                    ls_matnr-high =    ls_select_options-high.
                    APPEND ls_matnr TO lr_matnr.
        
                  ENDLOOP.
                ENDIF.
        
        
                SELECT *  FROM mara INTO CORRESPONDING FIELDS OF TABLE maraheader WHERE matnr IN lr_matnr.
                IF maraheader IS NOT INITIAL.
                  SELECT  * FROM marc INTO CORRESPONDING FIELDS OF TABLE maratomarc WHERE matnr IN lr_matnr.
                ENDIF.
                IF maratomarc IS NOT INITIAL .
                  SELECT * FROM mard INTO CORRESPONDING FIELDS OF TABLE maratomard  WHERE matnr IN lr_matnr.
                ENDIF.
                IF sy-subrc EQ 0.
                  LOOP AT maraheader INTO ls_mara.
                    MOVE-CORRESPONDING ls_mara TO ls_deepentity.
                    LOOP AT maratomarc INTO ls_marc.
                      APPEND ls_marc TO maratomarc_temp.
                      CLEAR :ls_marc.
                    ENDLOOP.
                    LOOP AT maratomard INTO ls_mard.
                      APPEND ls_mard TO maratomard_temp.
                      CLEAR: ls_mard.
                    ENDLOOP.
                    ls_deepentity-maratomarc = maratomarc_temp.
                    ls_deepentity-maratomard = maratomard_temp.
                    APPEND  ls_deepentity TO lt_deepentity.
                    CLEAR:ls_deepentity ,ls_mara,maratomard_temp,maratomarc_temp.
                  ENDLOOP.
                ENDIF.
        
                IF lt_deepentity IS NOT INITIAL.
                  CALL METHOD copy_data_to_ref(
                    EXPORTING
                      is_data = lt_deepentity
                    CHANGING
                      cr_data = er_entityset ).
                  APPEND 'maraToMarc' TO et_expanded_tech_clauses.
                  APPEND 'maraToMard' TO et_expanded_tech_clauses.
                ENDIF.
        
            ENDCASE.
        
        *****************************************end of dpc class****************************************************
        
        
        ***********start of mpc class******************
         METHOD header_mara.
            DATA: lo_entity_type  TYPE REF TO /iwbep/if_mgw_odata_entity_typ,
                  lo_entity_set   TYPE REF TO /iwbep/if_mgw_odata_entity_set,
                  lo_property     TYPE REF TO /iwbep/if_mgw_odata_property,
                  lo_assoc_set    TYPE REF TO /iwbep/if_mgw_odata_assoc_set, "#EC NEEDED
                  lo_nav_property TYPE REF TO /iwbep/if_mgw_odata_nav_prop. "#EC NEEDED
        
        
        
        
        * Define Entity MaraHead
            TRY.
                lo_entity_type = model->create_entity_type( iv_entity_type_name = 'maraHeader' iv_def_entity_set = abap_true ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Matnr' iv_abap_fieldname = 'MATNR' ). "#EC NOTEXT
                lo_property->set_conversion_exit('ALPHA').
                lo_property = lo_entity_type->create_property( iv_property_name = 'Ersda' iv_abap_fieldname = 'ERSDA' ). "#EC NOTEXT
        
                lo_property = lo_entity_type->create_property( iv_property_name = 'Ernam' iv_abap_fieldname = 'ERNAM' ). "#EC NOTEXT
                lo_property->set_conversion_exit('ALPHA').
                lo_property = lo_entity_type->create_property( iv_property_name = 'Laeda' iv_abap_fieldname = 'LAEDA' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Aenam' iv_abap_fieldname = 'AENAM' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Vpsta' iv_abap_fieldname = 'VPSTA' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Pstat' iv_abap_fieldname = 'PSTAT' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Lvorm' iv_abap_fieldname = 'LVORM' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Mtart' iv_abap_fieldname = 'MTART' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Mbrsh' iv_abap_fieldname = 'MBRSH' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Matkl' iv_abap_fieldname = 'MATKL' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Bismt' iv_abap_fieldname = 'BISMT' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Meins' iv_abap_fieldname = 'MEINS' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Bstme' iv_abap_fieldname = 'BSTME' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Zeinr' iv_abap_fieldname = 'ZEINR' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Zeiar' iv_abap_fieldname = 'ZEIAR' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Zeivr' iv_abap_fieldname = 'ZEIVR' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Zeifo' iv_abap_fieldname = 'ZEIFO' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Aeszn' iv_abap_fieldname = 'AESZN' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Blatt' iv_abap_fieldname = 'BLATT' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Blanz' iv_abap_fieldname = 'BLANZ' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Ferth' iv_abap_fieldname = 'FERTH' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Formt' iv_abap_fieldname = 'FORMT' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Groes' iv_abap_fieldname = 'GROES' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Wrkst' iv_abap_fieldname = 'WRKST' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Normt' iv_abap_fieldname = 'NORMT' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Labor' iv_abap_fieldname = 'LABOR' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Ekwsl' iv_abap_fieldname = 'EKWSL' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Brgew' iv_abap_fieldname = 'BRGEW' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Ntgew' iv_abap_fieldname = 'NTGEW' ). "#EC NOTEXT
                lo_property = lo_entity_type->create_property( iv_property_name = 'Gewei' iv_abap_fieldname = 'GEWEI' ). "#EC NOTEXT
                lo_entity_type->bind_structure( 'MARA' ).           "#EC NOTEXT
                lo_property = lo_entity_type->get_property( 'Matnr' ). "#EC NOTEXT
                lo_property->set_is_key( ).
        *        lo_property = lo_entity_type->create_property( iv_property_name = 'Ersda' iv_abap_fieldname = 'ERSDA' ). "#EC NOTEXT
        *        lo_property->set_is_key( ).
        *        lo_property = lo_entity_type->create_property( iv_property_name = 'Laeda' iv_abap_fieldname = 'LAEDA' ). "#EC NOTEXT
        *        lo_property->set_is_key( ).
              CATCH cx_root INTO DATA(d).
            ENDTRY.
          ENDMETHOD.
          ENDMETHOD.​
      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hello. I am not sure if I understand the question. As far as I remember, there is no restriction in OData which properties of an entity type may represent its key. So, also a date property (Edm.DateTime in OData V2) might work. The representation of the backend date (ABAP type d) is straightforward - see above.

      In the URL of course, you need to use the literal representation - see table above.

      In the MPC class you already made Matnr a key. Just try to do it the same way for 'Ersda'. The code you commented repeat 'create_property' but you already created the properties above.

      This type of question is best raised in the 'Answers' section of the SAP Community. Developers check that area quite frequently and you may get a more targeted answer if colleagues stumbled across similar issues. They could provide their solution or hints how to solve an underlying problem.

      Author's profile photo Swen Koenig
      Swen Koenig

      Hi Thomas Nitschke, Hi community,

      recently I had an issue with a Edm.DateTime conversion in an OData V2 property. In some cases the OData service implementation returned value 0 as ABAP timestamp for this property. In our production system this caused an /IWCOR/CX_DS_EDM_FACET_ERROR SAP Gateway error, because "Value '0 ' violates facet information 'nullable=false'".

      Thanks to this blog post, I understand how the conversion works and that nullable needs to be set to true in the OData Entity. The issue is now fixed and now also a correct initial timestamp ('00000000000000') is used.

      However I'm still wondering, why we don't get this SAP Gateway error in our test system. Both systems are on the same software versions. The test system seems to ignore the conversion error or does not do a check at all.

      Does anybody now, why these systems behave different? Is there any system setting to disable these checks?

      Kind regards,

      Swen

       

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hello Swen Koenig,

      Thanks for the question. At least, I am not aware of such a system-dependent difference. Strange. Maybe you try to re-post the question in the 'Answers' section of the community.

      Thomas

      Author's profile photo Klaus Reiner
      Klaus Reiner

      Hi Thomas,

      we are facing a requirement that sounds pretty simple:

      Calling an existing oData-Service with the current date.
      At least with the year-ID (YYYY) of current date.

      static version that works:
      ....$filter=YearCreated eq ‘2023’

      but we need it dynamically:
      ....$filter=YearCreated eq year(now())

      => unfortunatelly the dynamic version does not work.

      Do you have an idea how to derive current year
      to pass it as a filter value to odata services?

      Thank you!

      Kind regards,
      Klaus

       

       

      Kind regards,
      Klaus

      Author's profile photo Thomas Nitschke
      Thomas Nitschke
      Blog Post Author

      Hello Klaus,

      Nice example. I do not have an immediate answer - most probably those dynamic functions are not implemented. I forwarded the question to a colleague.

      But you might want to raise it in the 'Answers' section of the community as it gets more attention.

      Thomas