Introduction

Some dates in Active Directory have Generalized-Time syntax. These dates are strings in the format "YYYYMMDDhhmmss.0Z". For example "20141006133000.0Z" corresponds to October 6, 2014, at 13:30:00 (in UTC). This article dicusses how to deal with these dates.


AD Generalized-Time Attributes

Some of the Active Directory (AD) attributes with Generalized-Time syntax are documented in the following table. Sometimes this syntax is called UTC Coded time, because the values are stored in AD in UTC (Coordinated Universal Time, which used to be called GMT). "Replicated" in the table means the attribute is replicated to all domain controllers in the domain. "In GC" means the attribute is replicated to the Global Catalog. "Operational" means the attribute is not actually saved in AD, but is constructed on request from other attributes.

LDAPDisplayName PowerShell Property Replicated In GC Operational
createTimeStamp <none> Yes No Yes
meetingEndTime <none> Yes No No
meetingStartTime <none> Yes No No
modifyTimeStamp <none> Yes No Yes
whenChanged Modified No Yes No
whenCreated Created Yes Yes No

The meetingStartTime and meetingEndTime attributes only apply to objects of class Meeting. All of the other attributes in the above table apply to all objects in AD. The PowerShell properties Created and Modified are supported by the Active Directory module cmdlets, like Get-ADUser, Get-ADComputer, and Get-ADObject. These properties convert the values of the actual createTimeZone and modifyTimeZone attributes into datetime values in the local time zone. All of the attributes in the above table can only be updated by the system, except for meetingEndTime and meetingStartTime.

↑ Return to Top


The "*TimeStamp" Attributes versus the "when*" Attributes

The attributes createTimeStamp and whenCreated serve the same purpose. The same applies to the attributes modifyTimeStamp and whenChanged. The whenCreated and whenChanged attributes were added to the schema first by Microsoft. Then the createTimeStamp and modifyTimeStamp attributes were added to conform with LDAP RFC 4512.

As shown in the table above, the Active Directory schema tells us that createTimeStamp and modifyTimeStamp are replicated to all domain controllers, but are not in the Global Catalog. The whenChanged attribute is not replicated, but somehow is in the Global Catalog, while whenCreated is replicated and in the GC.

Notice that createTimeStamp and modifyTimeStamp are both operational (sometimes called constructed). This means the values are not saved in AD, but are constructed by the domain controller when you request the values. In this case, createTimeStamp is constructed from whenCreated and modifyTimeStamp is constructed from whenChanged. That explains why the values of createTimeStamp and whenCreated are the same for any object, as long as you query one domain controller. The values of whenChanged and modifyTimeStamp also agree on any given domain controller.

The one exception to the statement that whenChanged and modifyTimeStamp for any object always agree on any domain controller is the "cn=Aggregate" object in the Schema container. This is discussed in a blog post by Christoffer Andersson, linked below in the "Other Resources" section.

The values for the whenCreated attribute (and createTimeStamp) for any given object are the same on every domain controller. This is because whenCreated is replicated to all DC's. However, whenChanged is not replicated. The value of whenChanged (and modifyTimeStamp) on each domain controller reflects when the last change was replicated to that DC. The values of whenChanged for any object can vary by a few seconds if the domain controllers are well connected. The differences will be larger if you have domain controllers in remote sites with slow connections.

↑ Return to Top


The whenChanged Attribute Can Vary Widely Among DCs

The whenChanged values for some objects can be much larger if the change happened before some of the domain controllers were promoted. In that case, the value of whenChanged will reflect when the DC was promoted, which is when the last change actually replicated to that DC. The following table of example data illustrates this:

User Name whenChanged on DC1 whenChanged on DC2 whenChanged in DC3
jsmith 4/12/2014 13:08 4/12/2014 13:08 4/12/2014 13:08
sjohnson 6/21/2014 09:45 6/21/2014 09:45 6/21/2014 09:45
ljones 6/10/2013 11:41 11/12/2013 :15:31 2/15/2014 14:25
ajackson 2/23/2013 11:41 11/12/2013 :15:31 2/15/2014 14:25
fwilson 12/20/2013 10:09 12/20/2013 10:09 2/15/2014 14:25

From the table above, we see that user jsmith was last modified 4/12/2014, and this lastModified date is reflected on all three domain controllers. User sjohnson was last modified 6/21/2014. But the lastModified dates are different on each DC for users ljones and ajackson. This is because these objects were last modified before DC2 and DC3 were promoted to domain controllers. In fact, we can conclude that DC2 was promoted on 11/12/2013, since all users last modified before this date have this exact value for the whenChanged attribute on DC2. That must be the date when these objects were first synchronized with AD on that domain controller. Similarly, we can conclude that DC3 was promoted on 2/15/2014. Notice that user fwilson was last modified 12/20/2013, which is before DC3 was promoted, but after DC2 was promoted. The values of whenChanged for this user agree on domain controllers DC1 and DC2, which were in the domain when the change happened. But the value on DC3 for fwilson reflects when the user object was first replicated to that domain controller.

If you were to check the values of the whenCreated attribute for these users, you would see that the values are the same on all three domain controllers. Even if the whenCreated dates are before the domain controller was promoted, the correct value synchronizes from the other domain controllers during promotion.

↑ Return to Top


The "*TimeStamp" Attributes in the Global Catalog

Even though the schema indicates that createTimeStamp and modifyTimeStamp are not in the Global Catalog, you can still query the GC for these attributes because they are operational (also called constructed). Ordinarily, you cannot retrieve the value of an attribute from the Global Catalog if the value is not replicated to the GC. For example, you cannot retrieve the value of the department attribute from a GC. However, if the attribute is operational, the GC will construct the value when you request it. In this case, the values can be constructed from whenCreated or whenChanged, which are in the GC. However, since whenChanged is not replicated, you will only see values for this attribute in a GC for objects in the domain of the DC with the GC role.

↑ Return to Top


Retrieving Dates in Generalize-Time Format

The PowerShell Get-Date cmdlet supports a -UFormat parameter that can be used to retrieve the current date/time in Generalized-Time format. For example:

$Date = Get-Date -UFormat %Y%m%d%H%M%S.0Z
$Date

However, this cannot be used to convert any other datetime value into Generalized-Time format. For example, each of the following attempts raises an error:

$Date = (Get-Date -UFormat %Y%m%d%H%M%S.0Z).AddDays(-30)
$Date = (Get-Date).AddDays(-30) -UFormat %Y%m%d%H%M%S.0Z

Instead, a PowerShell function is required to convert any other date into Generalized-Time. For example:

Function PsDateToGenTime($Date)
{
    # Convert PowerShell datetime to Generalized-Time.
    # Ignore hours, minutes, and seconds.
    Return $Date.Year.ToString() `
        + ("0" + $Date.Month.ToString()).Substring($($Date.Month.ToString()).Length - 1, 2) `
        + ("0" + $Date.Day.ToString()).Substring($($Date.Day.ToString()).Length - 1, 2) `
        + "000000.0Z"
}

# Retrieve PowerShell date 30 days in the past. $PSDate = (Get-Date).AddDays(-30) "PowerShell Date: $PSDate"
# Convert to GeneralizeTime value. $GTDate = PsDateToGenTime $PSDate "Generalized-Time: $GTDate"

VBScript function for the same purpose is similar:

Function DateToGenTime(dtmValue)
    ' Function to convert datetime value into Generalized-Time.
    ' Ignore hours, minutes, and seconds.
    DateToGenTime = CStr(Year(dtmValue)) _
        & Right("0" & CStr(Month(dtmValue)), 2) _
        & Right("0" & CStr(Day(dtmValue)), 2) _
        & "000000.0Z"
End Function

' Retrieve a date 30 days in the past. dtmDate = DateAdd("d", -30, Now()) Wscript.Echo "VBScript Date: " & CStr(dtmDate)
' Convert to Generalized-Time value. Wscript.Echo "Generalized-Time: " & DateToGenTime(dtmDate)

These functions only deal with the date part, ignoring the time. If you modify the functions to deal with the time as well, then you must convert the datetime values from UTC to local time before converting to Generalized-Time.

↑ Return to Top


LDAP Filter Syntax

The LDAP filter syntax is supported in PowerShell, VBScript, and many command line utilities like dsquery * and adfind. In PowerShell, many AD module cmdlets support the -LDAPFilter parameter. The PowerShell code to retrieve all users created in the last 30 days using the -LDAPFilter parameter would be as follows:

$GTDate = "20141012133500.0Z"
Get-ADUser -LDAPFilter "(whenCreated>=$GTDate)" -Properties whenCreated | Select sAMAccountName, whenCreated

Notice the following about LDAP syntax filters:

  • The filter is a quoted string with each clause in parentheses (there is only one clause in the above example).
  • The supported operators include "=", ">=", and "<=". The operators ">" and "<" are not supported.
  • Clauses can be combined with the following operators: "&" (the AND operator), "|" (the OR operator), and "!" (the NOT operator).
  • Only Active Directory attributes, designated by their LDAPDisplayName, are recognized. The PowerShell properties, such as Created and Modified, are not suppported.
  • PowerShell datetime values, such as those retrieved using the Get-Date cmdlet are not supported. The datetime values must be converted into the Generalized-Time format.

The following table shows some example LDAP syntax filters with Generalized-Time attributes. The table shows filters with the whenChanged attribute, but the results are the same with any Generalized-Time attribute.

Filter Result
-LDAPFilter "(whenChanged>=$GTDate)" Works
-LDAPFilter '(whenChanged>=$GTDate)' No Results
-LDAPFilter "(whenChanged>='$GTDate')" No Results
-LDAPFilter '(whenChanged>="$GTDate")' No Results
-LDAPFilter "(whenChanged>=20141006123500.0Z)" Works

Some of the filters above do not raise an error but never produce results. Clearly, the filter should be enclosed in double quotes, and any variables or constants should not be quoted.

↑ Return to Top


PowerShell Filter Syntax

PowerShell documentation indicates that PowerShell syntax filters should be enclosed in braces. However, there are many examples where single quotes or double quotes are used instead. The following table shows some example PowerShell syntax filters with Generalized-Time attributes. Only examples using braces are shown.

Filter Result
-Filter {Modified -ge $PSDate} Works
-Filter {Modified -ge "$((Get-Date).AddDays(-30))"} No Results
-Filter {Modified -ge "09/26/2014 16:06:22"} Works
-Filter {Modified -ge '09/26/2014 16:06:22'} Works
-Filter {whenChanged -ge $PSDate} Works
-Filter {modifyTimeStamp -ge $PSDate} Works

↑ Return to Top


See Also


Other Resources

↑ Return to Top