Convert windows nanosecond epoch timestamp to human-readable date and vice versa
From Svendsen Tech Powershell Wiki
Sometimes as a Windows IT administrator, you will come across Windows epoch hundredth nanosecond time stamps (AD timestamp - also related are so-called "Windows ticks"). I forgot why I wrote this app a couple of years ago, but I needed it at the time and I was dealing with Active Directory time stamps.
There's a pure PowerShell example here.
I'm now making it easily available. I originally wrote it in Perl, but have since then put up a Python version as well. Read more about Perl on Windows here and also check out Perl from PowerShell.
The functions win_to_unix_epoch() and unix_to_win_epoch(), available for scrutiny in the Perl source code, also demonstrate how to convert between Windows and UNIX epoch, both ways. There's a "magic number" of seconds that makes up the difference between the Windows and UNIX epoch (11644473600).
If you provide a nanosecond timestamp, get a date, and convert it back to a nanosecond timestamp, you will notice that you lose precision beyond seconds. This is a limitation due to how the programs work with seconds at some point.
Contents |
Perl Version
Integrated Help Text
PS C:\> .\Conv-WinTime.exe
Usage: Conv-WinTime.exe [--help] [--nano2human "100th nanoseconds"]
[--human2nano "year-month-day hour:minute:second"]
--help : Print this help text.
--nano2human : Enter 100th nanoseconds as used by for instance the
lastLogon LDAP/AD attribute on Windows, and get a human-
readable date back.
--human2nano : Enter a human-readable date in the format:
"year-month-day hour:minute:second", and get the 100th
nanosecond timestamp back.
Author: Joakim Svendsen, "joakimbs" using Google's mail service.
ERROR: Please specify either --nano2human, --human2nano or both
Example Use Of Perl Conv-WinTime Version
PS C:\> perl .\Conv-WinTime.pl --human2nano '2012-12-24 00:00:00' Date: 2012-12-24 00:00:00 | 100th ns (Wintime): 130007772000000000
And to convert it back:
PS C:\> .\Conv-WinTime.exe --nano2human 130007772000000000 100th ns (Wintime): 130007772000000000 | Date: 2012-12-24 00:00:00
To get a little more fancy, and involve PowerShell, you can convert multiple dates to nanosecond timestamps (or the other way around) like this (or similarly):
PS C:\> '2012-12-24 00:00:00', '2013-12-24 00:00:00' | %{ .\Conv-WinTime.exe --human2nano $_ }
Date: 2012-12-24 00:00:00 | 100th ns (Wintime): 130007772000000000
Date: 2013-12-24 00:00:00 | 100th ns (Wintime): 130323132000000000
A custom-formatted Get-Date will also do:
PS C:\> Get-Date -uformat '%Y-%m-%d %H:%M:%S' | %{ .\Conv-WinTime.exe --human2nano $_ }
Date: 2012-03-17 15:05:30 | 100th ns (Wintime): 129764667300000000
To get just the nanoseconds without all the other stuff, you can do it in a ton of ways. This one's probably the simplest, using the -split operator and array indexing to get the last whitespace-separated element, indexed by [-1]:
PS C:\> '2012-12-24 00:00:00', '2013-12-24 00:00:00' |
>> %{ ((.\Conv-WinTime.exe --human2nano $_) -split '\s+')[-1] }
>>
130007772000000000
130323132000000000
Perl Downloads
- Conv-WinTime.zip - Packaged Perl executable. I promise it does nothing more than advertised here...
- Perl Script Source Code for Conv-WinTime - right-click, "save as" and rename to .pl. If you want to package this source code in a stand-alone Windows executable yourself, see this article.
Perl Source Code
use warnings;
use strict;
use Getopt::Long;
use File::Basename;
use Time::Local;
our $VERSION = '1.01';
my $debug = 1;
main();
sub main {
my %opt;
GetOptions('help' => \$opt{help},
'nano2human=s' => \$opt{nano2human},
'human2nano=s' => \$opt{human2nano},
) or die "Errors encountered while parsing command line options: $! -- $^E\n";
parse_args(\%opt);
if ($opt{nano2human}) {
print_help("Malformed nano time (non-digits found): $opt{nano2human}\n") if $opt{nano2human} =~ /\D/;
nano2human($opt{nano2human});
}
if ($opt{human2nano}) {
human2nano($opt{human2nano});
}
}
sub print_help {
my @message = @_;
my $script_name = basename $0;
print <<EOF;
$script_name v$VERSION
Usage: $script_name [--help] [--nano2human "100th nanoseconds"]
[--human2nano "year-month-day hour:minute:second"]
--help : Print this help text.
--nano2human : Enter 100th nanoseconds as used by for instance the
lastLogon LDAP/AD attribute on Windows, and get a human-
readable date back.
--human2nano : Enter a human-readable date in the format:
"year-month-day hour:minute:second", and get the 100th
nanosecond timestamp back.
Author: Joakim Svendsen, "joakimbs" using Google's mail service.
EOF
print @message, "\n" if @message;
exit;
}
sub nano2human {
my $wintime = shift;
my $unix_epoch = win_to_unix_epoch($wintime);
my ($year, $month, $day, $hour, $minute, $second) = (localtime $unix_epoch)[5,4,3,2,1,0];
$year += 1900;
$month += 1;
($month, $day, $hour, $minute, $second) = map { sprintf '%02d', $_ } $month, $day, $hour, $minute, $second;
print "100th ns (Wintime): $wintime | Date: " . join('-', $year, $month, $day) . ' ' . join(':', $hour, $minute, $second);
print "\n";
}
sub human2nano {
my $date = shift;
if ($date =~ /^(\d{4})-(\d\d)-(\d\d)\s+(\d\d):(\d\d):(\d\d)$/) {
my ($year, $month, $day, $hour, $minute, $second) = ($1, $2, $3, $4, $5, $6);
# Conform to localtime() standards as used by Time::Local
$year -= 1900;
$month -= 1;
my $unix_epoch = timelocal($second, $minute, $hour, $day, $month, $year);
my $win_epoch = unix_to_win_epoch($unix_epoch);
print "Date: $date | 100th ns (Wintime): $win_epoch\n";
}
else {
print_help(qq{ERROR: Malformed date specified: $date\nValid example: '2000-12-24 20:00:00'.\nRemember double quotes ("") around the --human2nano argument});
}
}
sub parse_args {
my $opt = shift;
print_help('') if $opt->{help};
unless ($opt->{nano2human} or $opt->{human2nano}) {
print_help("Warning: Please specify either --nano2human, --human2nano or both\n");
}
}
sub win_to_unix_epoch {
# Actually hundreths of nanoseconds at this point...
my $nanoseconds = shift;
# Get seconds
my $seconds = $nanoseconds / 10_000_000;
# This magic number is the difference between Unix and Windows epoch.
my $unix_epoch = $seconds - 11644473600;
# Return the Unix epoch for use with localtime().
return $unix_epoch;
}
sub unix_to_win_epoch {
my $unix_epoch = shift;
my $winseconds = $unix_epoch + 11644473600;
my $win_epoch = sprintf '%.0f', ($winseconds * 10_000_000);
return $win_epoch;
}
Pure PowerShell Approach
Here are some PowerShell examples for converting to and from AD timestamps based on the Windows NT epoch. The parsing of date strings apparently depends on locale settings, but something like the ISO standard yyyy-MM-ddTHH-mm-ss (year-month-day T 24hour-minute-second) should work reliably.
PS C:\> (Get-Date -Date "2013-12-24T00:00:00").ToFileTime() 130323132000000000
You can also cast to a datetime object and use the ToFileTime() method like this:
PS C:\> [datetime]'2013-12-31T21:00:00' Tuesday, December 31, 2013 9:00:00 PM PS C:\> ([datetime]'2013-12-31T21:00:00').ToFileTime() 130329936000000000
You can get "ticks" - which are from January 1st, year 1 - like this:
PS C:\> (Get-Date -Date "2013-12-24 00:00:00").Ticks 635234400000000000
To convert the AD time stamp back, you can use a static method, called FromFileTime(), on a temporarily instantiated DateTime object.
PS C:\> [DateTime]::FromFileTime('130323132000000000')
Tuesday, December 24, 2013 12:00:00 AM
Python Version Of Conv-WinTime
The Python version is similar to the Perl version. It uses the time.mktime() function, so it assumes the current time locale / time zone. Use calendar.timegm() for UTC (see source code below).
Example Use Of Python Conv-WinTime Version
To convert a date to a Windows nanosecond (AD) timestamp with the Python script version, you specify it according to an ISO standard (I should change the Perl script to use the same standard...) that looks like this: yyyy-MM-ddTHH:mm:ss. An example would be "2013-12-31T17:00:00".
Here it is in action:
PS E:\Python> .\Conv-WinTime.py --date2nano '2013-01-01T00:00:00' 130014684000000000
Convert it back and verify it's correct:
PS E:\Python> .\Conv-WinTime.py --nano2date 130014684000000000 2013-01-01 00:00:00
Use PowerShell for this near "noop" operation ("it does nothing"), to further verify it works as expected:
PS E:\Python> [datetime]::FromFileTime( (.\Conv-WinTime.py --date2nano '2013-01-01T00:00:00') ) 01 January, 2013 00:00:00
Python Downloads
Here's the script:
- Conv-WinTimePy.txt (right-click and save, rename to .py).
Python Source Code
#!/usr/bin/env python
import datetime
import calendar
import time
import sys
import re
from optparse import OptionParser
# Author: Joakim Svendsen, "joakimbs" using Google's mail services.
# Copyright (c) 2013. Svendsen Tech. All rights reserved.
parser = OptionParser()
parser.add_option("-n", "--nano2date", type="long", dest="nano_time",
help="Specify a Windows nanosecond (AD) timestamp to convert to a date/time.")
parser.add_option("-d", "--date2nano", dest="human_datetime",
help="Specify a date and time string in the format: \"yyyy-MM-ddTHH:mm:ss\" to be converted to a \
Windows nanosecond (AD) timestamp. Example: \"2013-01-01T17:00:00\"")
(options, args) = parser.parse_args()
if options.nano_time:
seconds = options.nano_time / 10000000
epoch = seconds - 11644473600
dt = datetime.datetime(2000, 1, 1, 0, 0, 0)
print dt.fromtimestamp(epoch)
if options.human_datetime:
m = re.compile(r'^(\d{4})-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)$').match(options.human_datetime)
if m:
dt = datetime.datetime(*map(int, m.groups()))
#unix_timestamp = time.mktime(dt.timetuple())
windows_timestamp = long((time.mktime(dt.timetuple()) + 11644473600) * 10000000)
print windows_timestamp
else:
print "Invalid date format specified with --date2nano: " + options.human_datetime + \
".\nUse the --help parameter for more information."
