0

I'm parsing the times from AD. There are two formats, i.e., YMD LDAP timestamps for whenCreated, whenChanged, 18-digit LDAP/FILETIME timestamps for lastLogonTimestamp, pwdLastSet, etc. Because I need to analyze the data upon the time. It makes sense to get local time. Here are the two functions that I wrote to parse the two different formats. The calculation in the second function I referenced from Convert 18-digit LDAP Timestamps To Human Teadable Date Using Java

public static String parseLdapDate(String ldapDate) {
    String[] parts = ldapDate.split("[.]");
    String dateTimePart = parts[0];  //take the date string before .0Z
    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss");
    sdf.setTimeZone(TimeZone.getTimeZone("GMT"));  //Z means UTC time, to the local timezone
    SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 

    try {
        Date tempDate = sdf.parse(dateTimePart); //parse the string to a date
        return formatter.format(tempDate);  //format it as what we want
    } catch (ParseException ex) {
        System.out.println("Parsing LDAP Date exception \n");
        ex.printStackTrace();
    }
    return null;
}

public static String parseLdapTimestamp(String ldapTimestamp) {
    long nanoseconds = Long.parseLong(ldapTimestamp);  // 100 nanoseconds 
    long mills = (nanoseconds/10000000);  // To seconds
    long unix = (((1970-1601)*365)-3+Math.round((1970-1601)/4))*86400L;
    long timeStamp = mills - unix;

    Date date = new Date(timeStamp*1000L); // To milliseconds
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    sdf.setTimeZone(TimeZone.getTimeZone("GMT")); 

    return sdf.format(date);
}

I have an example 20150520143936.0Z, which is converted to "2015-05-20 16:39:36". For the example 131097986571852097, it is converted to "2016-06-07 18:44:17", while http://www.epochconverter.com/ldap tells me that it's the GMT time and the local time is "2016-06-07 20:44:17". I will get the local time if I comment the code of setting timezone.

So now I'm confused, sdf.setTimeZone(TimeZone.getTimeZone("GMT")); gives me local timezone or the universal time. I was thinking if AD stores whenCreated in the universal time, lastLogonTimestamp in local time. But in the functions I parse them as strings. There is no symbol about the timezone. If I comment this sentence in the second function, will I get local time for both attributes when I access an LDAP Directory in another place.

  • Commented the `setTimeZone` in the second function. When the parsed parameter is 0, i.e. 1601-01-01 00:00:00 GMT, the returned value is 1601-01-01 01:00:00. The true value is 1601-01-01, 00:53:28 in my timezone according to http://www.epochconverter.com/ldap. Approximately correct. – Monika Diao Jun 13 '16 at 13:48
  • Similar: [Converting a ldap date](http://stackoverflow.com/q/9806329/642706) – Basil Bourque Aug 11 '16 at 01:44

1 Answers1

2

In the second case, you're constructing a Date and then telling it to format that date in UTC - whereas in the first case, you're parsing it in UTC, but formatting it in local time. You're already assuming that the timestamp is stored as a number of ticks since the Unix epoch, which is a time zone neutral format.

If the aim is to produce a string representation in local time, then you should remove the sdf.setTimeZone call. I would argue that a parse method should be returning a Date anyway though, rather than a String. Or better yet, return a java.time.Instant...

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • thanks. That makes it clear. So the point here is when I construct a `Date` from the milliseconds, it returns me the result upon the local machine? What I don't understand is when I set the timezone to "GMT", it points me to "GMT+00" again. – Monika Diao Jun 08 '16 at 13:42
  • @MonikaDiao: No, constructing a `Date` from milliseconds is always "milliseconds since the Unix epoch". The system default time zone is irrelevant. If you're basing your comment on the result of `Date.toString()`, ignore it - that converts the `Date` value (which is only an instant in time, after all) into the system default time zone. But that doesn't mean there's any time zone information in the `Date` object itself. – Jon Skeet Jun 08 '16 at 13:44
  • then it's when I format it to String that it is converted to the system's default time zone. What about the GMT setting? In the first function, I format a string to a date string that I want. No timezone is concerned. When I set the GMT, it seems to treat the date string at the time GMT+00 and returns me the local time which is GMT+02. In the second function, the date string is already the local time, it returns me the time at GMT+00. – Monika Diao Jun 08 '16 at 14:00
  • @MonikaDiao: I don't know what you mean by "the GMT setting". In the first method there *is* a time zone involved - it's just that `SimpleDateFormat` defaults to the system default time zone. I think you really need to focus on one part of it at a time and make sure you thoroughly understand that. Stack Overflow works best when each post is about one very specific problem - and there shouldn't be any need for longish comment threads like this. If you just want the value as a string in the local time zone, just remove the `sdf.setTimeZone` call from the second method. – Jon Skeet Jun 08 '16 at 14:03
  • @MonikaDiao: If you want anything else, you should be more specific, only talk about one example at a time, and provide a [mcve]. Note that for the second example, you can remove the `parseLong` part - just hard-code the value *as* a `long` to start with. Be very precise about input, expected output and actual output. – Jon Skeet Jun 08 '16 at 14:04
  • As the solution, I did remove the `sdf.setTimeZone` in the second function. And as you explained, it will always returns me a local time. What I don't understand is that why when I include it, it's not the default local time anymore, which was also in my question. And thanks for the tip, but I cannot remove the `parseLong` since the input here is the attribute value retrieved from Active Directory, which I processed as strings. – Monika Diao Jun 08 '16 at 14:25
  • 1
    @MonikaDiao: I have no idea what you mean by "it's not the default local time anymore"... but when I was suggesting removing the `parseLong` call, I meant *for a [mcve]*. In that case, the act of parsing a string into a `long` isn't part of the problem, so it shouldn't be part of the example. This sort of thing is crucial in being able to diagnose issues, and also for presenting issues on Stack Overflow. – Jon Skeet Jun 08 '16 at 14:36