Converting a ldap date

2020-02-10 14:52发布

问题:

I'm exporting users from an ldap programmatically. Therefor I'm retrieving the users from ldap. One of the attributes is whenCreated.

One of the values I have to convert is: 20090813145607.0Z Directly splitting it up I get the following format: yyyyMMddHHmmss+.0Z. The problem is that the application is running in CET timezone and the time stored is UTC which is probably indicated by the .0Z . It is 14:56 UTC and the local representation is 16:56. For summer time it seems to be 2 hours and for winter time 1 hour.

I checked the SimpleDateFormat and there is a placeholder for the timezone, however its a different format.

SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
sdf.parse("20090813145607.0Z");

Will show the wrong date as it ignores the dates time zone.

Is there a way to convert it directly?

回答1:

ISO 8601

As a couple of other Answers mentioned, the date-time format in question is defined by RFC 4517 Lightweight Directory Access Protocol (LDAP): Syntaxes and Matching Rules. See section 3.3.13, Generalized Time.

That section explains this LDAP format is a restricted version of one of the date-time formats defined by ISO 8601. This style using a minimum of separators is known as “basic” in ISO 8601.

In these formats, the Z on the end is short for Zulu and means UTC (basically same as GMT).

The decimal point and digit at the end represents a fraction of a second. Note that a comma is possible instead of the dot (period) in both RFC 4517 and ISO 8601. The comma is actually recommended over the dot in ISO 8601. The RFC 4517 spec allows for only a single digit fraction (some tenths of a fraction) or no dot/comma & digit at all. Note that in contrast: (a) ISO 8601 allows for any number of fractional digits, and (b) java.time objects have nanosecond resolution for up to nine digits of fractional second.

java.time

The java.time framework is built into Java 8 and later. These classes supplant the old troublesome date-time classes such as java.util.Date, .Calendar, & java.text.SimpleDateFormat.

Now in maintenance mode, the Joda-Time project also advises migration to java.time.

To learn more, see the Oracle Tutorial. And search Stack Overflow for many examples and explanations.

Much of the java.time functionality is back-ported to Java 6 & 7 in ThreeTen-Backport and further adapted to Android in ThreeTenABP.

The ThreeTen-Extra project extends java.time with additional classes. This project is a proving ground for possible future additions to java.time.

Parsing

Define a formatting pattern to fit RFC 4517. Study the DateTimeFormatter class for the pattern coding. This should work: uuuuMMddHHmmss[,S][.S]X. The square brackets mean optional. We accommodate either a dot or comma. Note the singular digit for fraction of second. The X on the end allows for either a Z or an offset-from-UTC such as -08 or -0830 or -08:30 or -083015 or -08:30:15.

String input = "20090813145607.0Z";
DateTimeFormatter f = DateTimeFormatter.ofPattern ( "uuuuMMddHHmmss[,S][.S]X" );
OffsetDateTime odt = OffsetDateTime.parse ( input , f );
Instant instant = odt.toInstant ();

Dump to console.

System.out.println ( "input: " + input + " | odt: " + odt + " | instant: " + instant );

input: 20090813145607.0Z | odt: 2009-08-13T14:56:07Z | instant: 2009-08-13T14:56:07Z

Of course you should also be coding a check for java.time.format.DateTimeParseException in case of unexpected input.



回答2:

Checking the RFC mentioned above it seems like using UTC is the recommended default behavior for ldap dates. Therefor I converted it directly:

public Date parseLdapDate(String ldapDate){
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));

    try {
        return sdf.parse(ldapDate);
    } catch (ParseException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    return null;
}


回答3:

What about using the split you described above, then reformatting the 0Z timezone into a standard format, then using sdf.parse(...)? Maybe something like this (with appropriate error checking added, of course):

String[] parts = inputDateTime.split("[.]");
String dateTimePart = parts[0];
String timeZonePart = "+0" + parts[1].substring(0, parts[1].length() - 1) + "00";
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssZ");
Date theDate = sdf.parse(dateTimePart + timeZonePart);


回答4:

The syntax of the attribute is described in the directory schema. Applications must use the schema when converting, comparing, and ordering data that was retrieved from or stored in the directory. If the syntax of the whenCreated attribute is generalizedTime, then applications must use libraries for generalized time when converting. The syntax for generalizedTime is described in RFC4517.



回答5:

You can use the methods of org.apache.directory.shared.ldap.util.DateUtils:

String ldapDate="20090813145607.0Z";

Date date = DateUtils.parse(ldapDate);

String generalizedTime = DateUtils.getGeneralizedTime(date);



回答6:

I tried to use the apache util GeneralizedTime class http://directory.apache.org/api/gen-docs/1.0.0-M11/apidocs/org/apache/directory/shared/util/GeneralizedTime.html with mixed results

to convert from current time to Active Direcotry format:

GeneralizedTime gt = new GeneralizedTime(Calendar.getInstance());
String gtADString = gt.toGeneralizedTime(GeneralizedTime.Format.YEAR_MONTH_DAY_HOUR_MIN_SEC_FRACTION, GeneralizedTime.FractionDelimiter.DOT, 1, GeneralizedTime.TimeZoneFormat.Z).replaceFirst("Z", "\\.0Z");

The only problem is that it does not work as advertised. The length of the fraction portion after the dot is supposed to be "1" according to this call but the result still comes out as 3. Instead of "20120410011958.6Z" I get "20120410011958.687Z" so I still have to get the time in seconds and insert ".0" before the Z. So here's what you have to do (in my case I don't care about the fraction so I put zero. AD cares)

GeneralizedTime gt = new GeneralizedTime(Calendar.getInstance());
String gtADString = gt.toGeneralizedTime(GeneralizedTime.Format.YEAR_MONTH_DAY_HOUR_MIN_SEC, GeneralizedTime.FractionDelimiter.DOT, 1, GeneralizedTime.TimeZoneFormat.Z).replaceFirst("Z", "\\.0Z");

Incidentally this code converts from AD GeneralizedTime string format to Java Date

 GeneralizedTime gt = new GeneralizedTime(str);
 Date d = gt.getCalendar().getTime();


回答7:

This is the only piece of code that worked for me :

static String parseLdapDate(String ldapDate) {

            long nanoseconds = Long.parseLong(ldapDate);   // nanoseconds since target time that you want to convert to java.util.Date

            long mills = (nanoseconds / 10000000);

            long unix = (((1970 - 1601) * 365) - 3 + Math.round((1970 - 1601) / 4)) * 86400L;

            long timeStamp = mills - unix;

            Date date = new Date(timeStamp * 1000L); // *1000 is to convert seconds to milliseconds
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); // the format of your date
    //sdf.setTimeZone(TimeZone.getTimeZone("GMT")); // give a timezone reference for formating (see comment at the bottom
            String formattedDate = sdf.format(date);

            return  formattedDate;
        }


标签: java ldap