×
UX/Mobile/Responsive

ActiveDirectory and PeopleCode integration

By Larry Grey • March 21, 2009

This post walks you through converting between Active Directory date/times and PeopleCode date/times. This is a follow up to our previous post about ways of constructing LDAP Queries for use within PeopleCode.

One of the examples from the previous post was an LDAP query that would show you Active Directory accounts that will expire within a given period of time (see the previous post to understand this).

(&(objectCategory=person)(objectClass=user)(!sAMAccountType=805306370)(!accountExpires=0)
(!accountExpires=9223372036854775807)(!accountExpires<=currentTime )(accountExpires<=givenTime))

The calculations for figuring out the correct values for currentTime and givenTime were, as they say, “an exercise for the reader”. This post provides the answers to the exercise so you don’t have to do it yourself 🙂

You might think that being able to plug in text representations of datetimes (e.g. ISO 8601 formatting) would work, but no such luck. Active Directory stores datetimes as the number of 100 nano-second intervals since 1600-01-01, so we need to convert from PeopleCode datetime objects into the corresponding number of 100 nano-second intervals since 1600-01-01.

PeopleCode provides some great datetime conversion logic (adding/subtracting dates, etc.), but nothing at the level we need for calculating 100 nano-second intervals. This where the PeopleCode/Java integration comes in handy. We can convert PeopleCode datetimes to java.util.Date objects. java.util.Date objects are defined as the number of milliseconds since 1970-01-01 (which is a negative number for dates earlier than that).

Then we just need to convert between milliseconds and 100 nano-seconds. One millisecond is one million nano-seconds, so one millisecond is ten thousand 100 nano-second intervals. So, we should be all set to put this into code.

We’ll start with our two top level functions for conversion.

/*
* These two functions convert between Active Directory datetime values
* and PeopleCode datetime objects in the local datetime
*/
Function ADDtTmToPCodeDtTm(&ADDate As number) Returns datetime
Local JavaObject &jADDate = ADDtTmToGMTJavaCalendar(&ADDate);
Return GMTJavaCalendarToPCodeDtTm(&jADDate);
End-Function;

Function PCodeDtTmToADDtTm(&pcDttm As datetime) Returns number
Local JavaObject &jADDate = PCodeDtTmToGMTJavaCalendar(&pcDttm);
Return GMTJavaCalendarToADDtTm(&jADDate);
End-Function;

Calling the PCodeDtTmToADDtTm function with a PeopleCode datetime object will return a value that is suitable for use in querying Active Directory. Here is some test code that exercises the functions.

/*
* Simple test function to verify that round tripping
* a datetime from PeopleCode to Active Directory and
* back gives the correct result.
*/
Function TestADDtTmToPCodeDtTm(&pcDttm As datetime)
Local number &adDttm = PCodeDtTmToADDtTm(&pcDttm);
Local datetime &testPCDttm = ADDtTmToPCodeDtTm(&adDttm);
If &testPCDttm <> &pcDttm Then
Error ("Bad ADDtTmToPCodeDtTm: " | &testPCDttm | " was not equal to " | &pcDttm);
End-If;
End-Function;

TestADDtTmToPCodeDtTm(%Datetime);
TestADDtTmToPCodeDtTm(DateTime6(2000, 1, 1, 1, 1, 1));

Now let’s take a look at the underlying conversion functions. We break these up into several distinct functions for each step of the conversion. This allows for easier testing of everything, as well as potential re-use if we are not starting from a PeopleCode datetime in the local timezone.


/*
* Get the number of 100 nano-second intervals between our bases;
* The first two lines show the actual calculation, but we just return
* the actual value since it will never change.
*/
Function get100NanosDelta() Returns number;
REM Local JavaObject &jBase = CreateJavaObject("java.util.Date", 1601, 0, 1);
REM &100nanosDelta = &jBase.getTime() * (10**4) * - 1;
Return 116444448000000000;
End-Function;

/* Utility function to construct GMT java.util.Calendar object */
Function CreateGMTCalendar() Returns JavaObject;
Local JavaObject &GMTTimeZone, &defLocale;
&GMTTimeZone = CreateJavaObject("java.util.SimpleTimeZone", 0, "GMT");
&defLocale = GetJavaClass("java.util.Locale").getDefault();
Return CreateJavaObject("java.util.GregorianCalendar", &GMTTimeZone, &defLocale);
End-Function;

/* These two functions convert between bases and change unit of measure */
Function ADDtTmToGMTJavaNumber(&ADDate As number) Returns number
Return (&ADDate - get100NanosDelta()) / 10000;
End-Function;

Function JavaNumberToADDtTm(&jDate As number) Returns number
Return (&jDate * 10000) + get100NanosDelta();
End-Function;


/*
* These two functions convert between Active Directory datetimes
* and java.util.Calendar objects
*/
Function ADDtTmToGMTJavaCalendar(&ADDate As number) Returns JavaObject;
Local JavaObject &jCalendar = CreateGMTCalendar();
&jCalendar.setTimeInMillis(ADDtTmToGMTJavaNumber(&ADDate));
Return &jCalendar;
End-Function;

Function GMTJavaCalendarToADDtTm(&jCalendar As JavaObject) Returns number;
Return JavaNumberToADDtTm(&jCalendar.getTimeInMillis());
End-Function;


/*
* These two functions convert between GMT java.util.Calendar objects
* and GMT PeopleCode datetime objects.
*
* See below for versions that deal with local timezones
*/
Function GMTJavaCalendarToGMTPCodeDtTm(&jCalendar As JavaObject) Returns datetime
Local JavaObject &c = GetJavaClass("java.util.Calendar");
Local number &year = &jCalendar.get(&c.YEAR);
Local number &month = &jCalendar.get(&c.MONTH) + 1;
Local number &day = &jCalendar.get(&c.DAY_OF_MONTH);
Local number &hour = &jCalendar.get(&c.HOUR);
Local number &minute = &jCalendar.get(&c.MINUTE);
Local number &seconds = &jCalendar.get(&c.SECOND);
Return DateTime6(&year, &month, &day, &hour, &minute, &seconds);
End-Function;

Function GMTPCodeDtTmToGMTJavaCalendar(&pcDttm As datetime) Returns JavaObject;
Local JavaObject &jCalendar = CreateGMTCalendar();
Local JavaObject &c = GetJavaClass("java.util.Calendar");
&jCalendar.set(&c.YEAR, Year(&pcDttm));
&jCalendar.set(&c.MONTH, Month(&pcDttm) - 1);
&jCalendar.set(&c.DAY_OF_MONTH, Day(&pcDttm));
&jCalendar.set(&c.HOUR, Hour(&pcDttm));
&jCalendar.set(&c.MINUTE, Minute(&pcDttm));
&jCalendar.set(&c.SECOND, Second(&pcDttm));
Return &jCalendar;
End-Function;


/*
* These two functions convert between GMT java.util.Calendar objects
* and PeopleCode datetime objects in the local timezone.
*
* See above for versions that deal with GMT PeopleCode datetime values
*/
Function GMTJavaCalendarToPCodeDtTm(&jCalendar As JavaObject) Returns datetime
Local datetime &utcDate = GMTJavaCalendarToGMTPCodeDtTm(&jCalendar);
Return DateTimeToTimeZone(&utcDate, "GMT", "Local");
End-Function;

Function PCodeDtTmToG MTJavaCalendar(&pcDttm As datetime) Returns JavaObject;
Local datetime &utcDate = DateTimeToTimeZone(&pcDttm, "Local", "GMT");
Return GMTPCodeDtTmToGMTJavaCalendar(&utcDate);
End-Function;

So the flow from PeopleCode to ActiveDirectory is


  1. Convert from local time zone to GMT

  2. Convert from PeopleCode to Java date

  3. Convert from Java date to milliseconds since 1970-01-01

  4. Convert from milliseconds to 100 nano-second intervals



If you query Active Directory and get back a datetime value and want to take action on that in PeopleCode, then the flow is reversed in ADDtTmToPCodeDtTm.

Here’s a little more example code that shows this in action.

/* This is Active Directory's "never expires" datetime */
Local number &jan1_1970 = 9223372036854775807;
Warning (&jan1_1970 | " is " | ADDtTmToPCodeDtTm(&jan1_1970));

Local number &expires = 128238516000000000;
Warning (&expires | " is (Java) " | ADDtTmToGMTJavaCalendar(&expires).toString());
Warning (&expires | " is (GMT PeopleCode) " | GMTJavaCalendarToGMTPCodeDtTm(ADDtTmToGMTJavaCalendar(&expires)));
Warning (&expires | " is (Local PeopleCode)" | ADDtTmToPCodeDtTm(&expires));

Local datetime &expiresPC = ADDtTmToPCodeDtTm(&expires);
Warning ("expiresPC is " | &expiresPC);

Labels: , , ,

Stay Updated

3 Replies to “ActiveDirectory and PeopleCode integration”

  1. Looks like the values are off by 12 hours. Is your server in a different time zone?

    Also, try doing it with a specific datetime value; there may be an issue with time zone conversions when using %Datetime and the server being elsewhere (%Datetime is the current server time, but the other time zone conversions are based on your user session).

  2. For some reason the round trip test in TestADDtTmToPCodeDtTm()
    is not working for me and for the life of me I can’t figure it out.

    Sending in 2009-05-12-08.18.20.000000 (MSTA)
    Results in &adDttm 128866331007510000
    and &testPCDttm 2009-05-12-20.18.20.000000

    when I plug 128866331007510000 into
    w32tm.exe /ntte {&adDttm}
    I get 149150 20:18:20.7510000 – 5/12/2009 1:18:20 PM (local time)

    Since 2009-05-12-08.18.20.000000
    2009-05-12-20.18.20.000000 the error statement is executed.

  3. Great topic and at a perfect time. Unfortunately I’m just not getting it. My results of the call to TestADDtTmToPCodeDtTm() is never successful. For example this morning I called it with
    %Datetime = 2009-05-12-08.18.20.000000
    and it returned
    128866331007510000 in &adDttm
    plugging 128866331007510000 into w32tm.exe /ntte {&adDttm}
    returns
    149150 20:18:20.7510000 – 5/12/2009 1:18:20 PM (local time)

    in the TestADDtTmToPCodeDtTm() function the reverse calculation of &adDttm=128866331007510000 to local yields
    &testPCDttm = 2009-05-12-20.18.20.000000

    Finally since 2009-05-12-20.18.20.000000 input value 2009-05-12-08.18.20.000000 the error is executed.

Comments are closed.

Request a Demo