#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright 2012 The Plaso Project Authors. # Please see the AUTHORS file for details on individual authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """This file contains a unit test for the timelib in Plaso.""" import calendar import datetime import unittest from plaso.lib import timelib import pytz def CopyStringToTimestamp(time_string): """Copies a string containing a date and time value to a timestamp. Test function that does not rely on dateutil parser. Args: time_string: A string containing a date and time value formatted as: YYYY-MM-DD hh:mm:ss.######[+-]##:## Where # are numeric digits ranging from 0 to 9 and the seconds fraction can be either 3 or 6 digits. Both the seconds fraction and timezone offset are optional. The default timezone is UTC. Returns: An integer containing the timestamp. Raises: ValueError: if the time string is invalid or not supported. """ time_string_length = len(time_string) # The time string should at least contain 'YYYY-MM-DD hh:mm:ss'. if (time_string_length < 19 or time_string[4] != '-' or time_string[7] != '-' or time_string[10] != ' ' or time_string[13] != ':' or time_string[16] != ':'): raise ValueError(u'Invalid time string.') try: year = int(time_string[0:4], 10) except ValueError: raise ValueError(u'Unable to parse year.') try: month = int(time_string[5:7], 10) except ValueError: raise ValueError(u'Unable to parse month.') if month not in range(1, 13): raise ValueError(u'Month value out of bounds.') try: day_of_month = int(time_string[8:10], 10) except ValueError: raise ValueError(u'Unable to parse day of month.') if day_of_month not in range(1, 32): raise ValueError(u'Day of month value out of bounds.') try: hours = int(time_string[11:13], 10) except ValueError: raise ValueError(u'Unable to parse hours.') if hours not in range(0, 24): raise ValueError(u'Hours value out of bounds.') try: minutes = int(time_string[14:16], 10) except ValueError: raise ValueError(u'Unable to parse minutes.') if minutes not in range(0, 60): raise ValueError(u'Minutes value out of bounds.') try: seconds = int(time_string[17:19], 10) except ValueError: raise ValueError(u'Unable to parse day of seconds.') if seconds not in range(0, 60): raise ValueError(u'Seconds value out of bounds.') micro_seconds = 0 timezone_offset = 0 if time_string_length > 19: if time_string[19] != '.': timezone_index = 19 else: for timezone_index in range(19, time_string_length): if time_string[timezone_index] in ['+', '-']: break # The calculation that follow rely on the timezone index to point # beyond the string in case no timezone offset was defined. if timezone_index == time_string_length - 1: timezone_index += 1 if timezone_index > 19: fraction_of_seconds_length = timezone_index - 20 if fraction_of_seconds_length not in [3, 6]: raise ValueError(u'Invalid time string.') try: micro_seconds = int(time_string[20:timezone_index], 10) except ValueError: raise ValueError(u'Unable to parse fraction of seconds.') if fraction_of_seconds_length == 3: micro_seconds *= 1000 if timezone_index < time_string_length: if (time_string_length - timezone_index != 6 or time_string[timezone_index + 3] != ':'): raise ValueError(u'Invalid time string.') try: timezone_offset = int(time_string[ timezone_index + 1:timezone_index + 3]) except ValueError: raise ValueError(u'Unable to parse timezone hours offset.') if timezone_offset not in range(0, 24): raise ValueError(u'Timezone hours offset value out of bounds.') # Note that when the sign of the timezone offset is negative # the difference needs to be added. We do so by flipping the sign. if time_string[timezone_index] == '-': timezone_offset *= 60 else: timezone_offset *= -60 try: timezone_offset += int(time_string[ timezone_index + 4:timezone_index + 6]) except ValueError: raise ValueError(u'Unable to parse timezone minutes offset.') timezone_offset *= 60 timestamp = int(calendar.timegm(( year, month, day_of_month, hours, minutes, seconds))) return ((timestamp + timezone_offset) * 1000000) + micro_seconds class TimeLibUnitTest(unittest.TestCase): """A unit test for the timelib.""" def testCocoaTime(self): """Tests the Cocoa timestamp conversion.""" self.assertEquals( timelib.Timestamp.FromCocoaTime(395011845), CopyStringToTimestamp('2013-07-08 21:30:45')) self.assertEquals( timelib.Timestamp.FromCocoaTime(395353142), CopyStringToTimestamp('2013-07-12 20:19:02')) self.assertEquals( timelib.Timestamp.FromCocoaTime(394993669), CopyStringToTimestamp('2013-07-08 16:27:49')) def testHFSTimes(self): """Tests the HFS timestamp conversion.""" self.assertEquals( timelib.Timestamp.FromHfsTime( 3458215528, timezone=pytz.timezone('EST5EDT'), is_dst=True), CopyStringToTimestamp('2013-08-01 15:25:28-04:00')) self.assertEquals( timelib.Timestamp.FromHfsPlusTime(3458215528), CopyStringToTimestamp('2013-08-01 15:25:28')) self.assertEquals( timelib.Timestamp.FromHfsPlusTime(3413373928), CopyStringToTimestamp('2012-02-29 15:25:28')) def testTimestampIsLeapYear(self): """Tests the is leap year check.""" self.assertEquals(timelib.Timestamp.IsLeapYear(2012), True) self.assertEquals(timelib.Timestamp.IsLeapYear(2013), False) self.assertEquals(timelib.Timestamp.IsLeapYear(2000), True) self.assertEquals(timelib.Timestamp.IsLeapYear(1900), False) def testTimestampDaysInMonth(self): """Tests the days in month function.""" self.assertEquals(timelib.Timestamp.DaysInMonth(0, 2013), 31) self.assertEquals(timelib.Timestamp.DaysInMonth(1, 2013), 28) self.assertEquals(timelib.Timestamp.DaysInMonth(1, 2012), 29) self.assertEquals(timelib.Timestamp.DaysInMonth(2, 2013), 31) self.assertEquals(timelib.Timestamp.DaysInMonth(3, 2013), 30) self.assertEquals(timelib.Timestamp.DaysInMonth(4, 2013), 31) self.assertEquals(timelib.Timestamp.DaysInMonth(5, 2013), 30) self.assertEquals(timelib.Timestamp.DaysInMonth(6, 2013), 31) self.assertEquals(timelib.Timestamp.DaysInMonth(7, 2013), 31) self.assertEquals(timelib.Timestamp.DaysInMonth(8, 2013), 30) self.assertEquals(timelib.Timestamp.DaysInMonth(9, 2013), 31) self.assertEquals(timelib.Timestamp.DaysInMonth(10, 2013), 30) self.assertEquals(timelib.Timestamp.DaysInMonth(11, 2013), 31) with self.assertRaises(ValueError): timelib.Timestamp.DaysInMonth(-1, 2013) with self.assertRaises(ValueError): timelib.Timestamp.DaysInMonth(12, 2013) def testTimestampDaysInYear(self): """Test the days in year function.""" self.assertEquals(timelib.Timestamp.DaysInYear(2013), 365) self.assertEquals(timelib.Timestamp.DaysInYear(2012), 366) def testTimestampDayOfYear(self): """Test the day of year function.""" self.assertEquals(timelib.Timestamp.DayOfYear(0, 0, 2013), 0) self.assertEquals(timelib.Timestamp.DayOfYear(0, 2, 2013), 31 + 28) self.assertEquals(timelib.Timestamp.DayOfYear(0, 2, 2012), 31 + 29) self.assertEquals(timelib.Timestamp.DayOfYear(0, 11, 2013), 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30) def testTimestampFromDelphiTime(self): """Test the Delphi date time conversion.""" self.assertEquals( timelib.Timestamp.FromDelphiTime(41443.8263953), CopyStringToTimestamp('2013-06-18 19:50:00')) def testTimestampFromFatDateTime(self): """Test the FAT date time conversion.""" self.assertEquals( timelib.Timestamp.FromFatDateTime(0xa8d03d0c), CopyStringToTimestamp('2010-08-12 21:06:32')) # Invalid number of seconds. fat_date_time = (0xa8d03d0c & ~(0x1f << 16)) | ((30 & 0x1f) << 16) self.assertEquals(timelib.Timestamp.FromFatDateTime(fat_date_time), 0) # Invalid number of minutes. fat_date_time = (0xa8d03d0c & ~(0x3f << 21)) | ((60 & 0x3f) << 21) self.assertEquals(timelib.Timestamp.FromFatDateTime(fat_date_time), 0) # Invalid number of hours. fat_date_time = (0xa8d03d0c & ~(0x1f << 27)) | ((24 & 0x1f) << 27) self.assertEquals(timelib.Timestamp.FromFatDateTime(fat_date_time), 0) # Invalid day of month. fat_date_time = (0xa8d03d0c & ~0x1f) | (32 & 0x1f) self.assertEquals(timelib.Timestamp.FromFatDateTime(fat_date_time), 0) # Invalid month. fat_date_time = (0xa8d03d0c & ~(0x0f << 5)) | ((13 & 0x0f) << 5) self.assertEquals(timelib.Timestamp.FromFatDateTime(fat_date_time), 0) def testTimestampFromWebKitTime(self): """Test the WebKit time conversion.""" self.assertEquals( timelib.Timestamp.FromWebKitTime(0x2dec3d061a9bfb), CopyStringToTimestamp('2010-08-12 21:06:31.546875')) webkit_time = 86400 * 1000000 self.assertEquals( timelib.Timestamp.FromWebKitTime(webkit_time), CopyStringToTimestamp('1601-01-02 00:00:00')) # WebKit time that exceeds lower bound. webkit_time = -((1 << 63L) - 1) self.assertEquals(timelib.Timestamp.FromWebKitTime(webkit_time), 0) def testTimestampFromFiletime(self): """Test the FILETIME conversion.""" self.assertEquals( timelib.Timestamp.FromFiletime(0x01cb3a623d0a17ce), CopyStringToTimestamp('2010-08-12 21:06:31.546875')) filetime = 86400 * 10000000 self.assertEquals( timelib.Timestamp.FromFiletime(filetime), CopyStringToTimestamp('1601-01-02 00:00:00')) # FILETIME that exceeds lower bound. filetime = -1 self.assertEquals(timelib.Timestamp.FromFiletime(filetime), 0) def testTimestampFromPosixTime(self): """Test the POSIX time conversion.""" self.assertEquals( timelib.Timestamp.FromPosixTime(1281647191), CopyStringToTimestamp('2010-08-12 21:06:31')) self.assertEquals( timelib.Timestamp.FromPosixTime(-122557518), timelib.Timestamp.FromTimeString('1966-02-12 1966 12:14:42 UTC')) # POSIX time that exceeds upper bound. self.assertEquals(timelib.Timestamp.FromPosixTime(9223372036855), 0) # POSIX time that exceeds lower bound. self.assertEquals(timelib.Timestamp.FromPosixTime(-9223372036855), 0) def testMonthDict(self): """Test the month dict, both inside and outside of scope.""" self.assertEquals(timelib.MONTH_DICT['nov'], 11) self.assertEquals(timelib.MONTH_DICT['jan'], 1) self.assertEquals(timelib.MONTH_DICT['may'], 5) month = timelib.MONTH_DICT.get('doesnotexist') self.assertEquals(month, None) def testLocaltimeToUTC(self): """Test the localtime to UTC conversion.""" timezone = pytz.timezone('CET') local_timestamp = CopyStringToTimestamp('2013-01-01 01:00:00') self.assertEquals( timelib.Timestamp.LocaltimeToUTC(local_timestamp, timezone), CopyStringToTimestamp('2013-01-01 00:00:00')) local_timestamp = CopyStringToTimestamp('2013-07-01 02:00:00') self.assertEquals( timelib.Timestamp.LocaltimeToUTC(local_timestamp, timezone), CopyStringToTimestamp('2013-07-01 00:00:00')) # In the local timezone this is a non-existent timestamp. local_timestamp = CopyStringToTimestamp('2013-03-31 02:00:00') with self.assertRaises(pytz.NonExistentTimeError): timelib.Timestamp.LocaltimeToUTC(local_timestamp, timezone, is_dst=None) self.assertEquals( timelib.Timestamp.LocaltimeToUTC( local_timestamp, timezone, is_dst=True), CopyStringToTimestamp('2013-03-31 00:00:00')) self.assertEquals( timelib.Timestamp.LocaltimeToUTC( local_timestamp, timezone, is_dst=False), CopyStringToTimestamp('2013-03-31 01:00:00')) # In the local timezone this is an ambiguous timestamp. local_timestamp = CopyStringToTimestamp('2013-10-27 02:30:00') with self.assertRaises(pytz.AmbiguousTimeError): timelib.Timestamp.LocaltimeToUTC(local_timestamp, timezone, is_dst=None) self.assertEquals( timelib.Timestamp.LocaltimeToUTC( local_timestamp, timezone, is_dst=True), CopyStringToTimestamp('2013-10-27 00:30:00')) self.assertEquals( timelib.Timestamp.LocaltimeToUTC(local_timestamp, timezone), CopyStringToTimestamp('2013-10-27 01:30:00')) # Use the UTC timezone. self.assertEquals( timelib.Timestamp.LocaltimeToUTC(local_timestamp, pytz.utc), local_timestamp) # Use a timezone in the Western Hemisphere. timezone = pytz.timezone('EST') local_timestamp = CopyStringToTimestamp('2013-01-01 00:00:00') self.assertEquals( timelib.Timestamp.LocaltimeToUTC(local_timestamp, timezone), CopyStringToTimestamp('2013-01-01 05:00:00')) def testCopyToDatetime(self): """Test the copy to datetime object.""" timezone = pytz.timezone('CET') timestamp = CopyStringToTimestamp('2013-03-14 20:20:08.850041') self.assertEquals( timelib.Timestamp.CopyToDatetime(timestamp, timezone), datetime.datetime(2013, 3, 14, 21, 20, 8, 850041, tzinfo=timezone)) def testCopyToPosix(self): """Test converting microseconds to seconds.""" timestamp = CopyStringToTimestamp('2013-10-01 12:00:00') self.assertEquals( timelib.Timestamp.CopyToPosix(timestamp), timestamp // 1000000) def testTimestampFromTimeString(self): """The the FromTimeString function.""" # Test daylight savings. expected_timestamp = CopyStringToTimestamp('2013-10-01 12:00:00') # Check certain variance of this timestamp. timestamp = timelib.Timestamp.FromTimeString( '2013-10-01 14:00:00', pytz.timezone('Europe/Rome')) self.assertEquals(timestamp, expected_timestamp) timestamp = timelib.Timestamp.FromTimeString( '2013-10-01 12:00:00', pytz.timezone('UTC')) self.assertEquals(timestamp, expected_timestamp) timestamp = timelib.Timestamp.FromTimeString( '2013-10-01 05:00:00', pytz.timezone('PST8PDT')) self.assertEquals(timestamp, expected_timestamp) # Now to test outside of the daylight savings. expected_timestamp = CopyStringToTimestamp('2014-02-01 12:00:00') timestamp = timelib.Timestamp.FromTimeString( '2014-02-01 13:00:00', pytz.timezone('Europe/Rome')) self.assertEquals(timestamp, expected_timestamp) timestamp = timelib.Timestamp.FromTimeString( '2014-02-01 12:00:00', pytz.timezone('UTC')) self.assertEquals(timestamp, expected_timestamp) timestamp = timelib.Timestamp.FromTimeString( '2014-02-01 04:00:00', pytz.timezone('PST8PDT')) self.assertEquals(timestamp, expected_timestamp) # Define two timestamps, one being GMT and the other UTC. time_string_utc = 'Wed 05 May 2010 03:52:31 UTC' time_string_gmt = 'Wed 05 May 2010 03:52:31 GMT' timestamp_utc = timelib.Timestamp.FromTimeString(time_string_utc) timestamp_gmt = timelib.Timestamp.FromTimeString(time_string_gmt) # Test if these two are different, and if so, then we'll try again # using the 'gmt_is_utc' flag, which then should result to the same # results. if timestamp_utc != timestamp_gmt: self.assertEquals(timestamp_utc, timelib.Timestamp.FromTimeString( time_string_gmt, gmt_as_timezone=False)) def testRoundTimestamp(self): """Test the RoundToSeconds function.""" # Should be rounded up. test_one = 442813351785412 # Should be rounded down. test_two = 1384381247271976 self.assertEquals( timelib.Timestamp.RoundToSeconds(test_one), 442813352000000) self.assertEquals( timelib.Timestamp.RoundToSeconds(test_two), 1384381247000000) def testTimestampFromTimeParts(self): """Test the FromTimeParts function.""" timestamp = timelib.Timestamp.FromTimeParts( 2013, 6, 25, 22, 19, 46, 0, timezone=pytz.timezone('PST8PDT')) self.assertEquals( timestamp, CopyStringToTimestamp('2013-06-25 22:19:46-07:00')) timestamp = timelib.Timestamp.FromTimeParts(2013, 6, 26, 5, 19, 46) self.assertEquals( timestamp, CopyStringToTimestamp('2013-06-26 05:19:46')) timestamp = timelib.Timestamp.FromTimeParts( 2013, 6, 26, 5, 19, 46, 542) self.assertEquals( timestamp, CopyStringToTimestamp('2013-06-26 05:19:46.000542')) def _TestStringToDatetime( self, expected_timestamp, time_string, timezone=pytz.utc, dayfirst=False): """Tests the StringToDatetime function. Args: expected_timestamp: The expected timesamp. time_string: String that contains a date and time value. timezone: The timezone (pytz.timezone) object. dayfirst: Change precedence of day vs. month. Returns: A result object. """ date_time = timelib.StringToDatetime( time_string, timezone=timezone, dayfirst=dayfirst) timestamp = int(calendar.timegm((date_time.utctimetuple()))) self.assertEquals(timestamp, expected_timestamp) def testStringToDatetime(self): """Test the StringToDatetime function.""" self._TestStringToDatetime( 471953580, '12-15-1984 05:13:00', timezone=pytz.timezone('EST5EDT')) # Swap day and month. self._TestStringToDatetime( 466420380, '12-10-1984 05:13:00', timezone=pytz.timezone('EST5EDT'), dayfirst=True) self._TestStringToDatetime(471953580, '12-15-1984 10:13:00Z') # Setting the timezone for string that already contains a timezone # indicator should not affect the conversion. self._TestStringToDatetime( 471953580, '12-15-1984 10:13:00Z', timezone=pytz.timezone('EST5EDT')) self._TestStringToDatetime(471953580, '15/12/1984 10:13:00Z') self._TestStringToDatetime(471953580, '15-12-84 10:13:00Z') self._TestStringToDatetime( 471967980, '15-12-84 10:13:00-04', timezone=pytz.timezone('EST5EDT')) self._TestStringToDatetime( 0, 'thisisnotadatetime', timezone=pytz.timezone('EST5EDT')) self._TestStringToDatetime( 471953580, '12-15-1984 04:13:00', timezone=pytz.timezone('America/Chicago')) self._TestStringToDatetime( 458712780, '07-14-1984 23:13:00', timezone=pytz.timezone('America/Chicago')) self._TestStringToDatetime( 471964380, '12-15-1984 05:13:00', timezone=pytz.timezone('US/Pacific')) if __name__ == '__main__': unittest.main()