fat_date_time.py 6.13 KB
Newer Older
1 2 3
# -*- coding: utf-8 -*-
"""FAT date time implementation."""

4 5
from __future__ import unicode_literals

6 7
import decimal

8 9 10 11
from dfdatetime import definitions
from dfdatetime import interface


12 13 14 15 16 17 18 19
class FATDateTimeEpoch(interface.DateTimeEpoch):
  """FAT date time time epoch."""

  def __init__(self):
    """Initializes a FAT date time epoch."""
    super(FATDateTimeEpoch, self).__init__(1980, 1, 1)


20
class FATDateTime(interface.DateTimeValues):
21
  """FAT date time.
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41

  The FAT date time is mainly used in DOS/Windows file formats and FAT.

  The FAT date and time is a 32-bit value containing two 16-bit values:
    * The date (lower 16-bit).
      * bits 0 - 4: day of month, where 1 represents the first day
      * bits 5 - 8: month of year, where 1 represent January
      * bits 9 - 15: year since 1980
    * The time of day (upper 16-bit).
      * bits 0 - 4: seconds (in 2 second intervals)
      * bits 5 - 10: minutes
      * bits 11 - 15: hours

  The FAT date time has no time zone information and is typically stored
  in the local time of the computer.

  Attributes:
    is_local_time (bool): True if the date and time value is in local time.
  """

42 43 44
  _EPOCH = FATDateTimeEpoch()

  # The difference between January 1, 1980 and January 1, 1970 in seconds.
45 46 47 48 49 50 51 52 53 54 55 56 57
  _FAT_DATE_TO_POSIX_BASE = 315532800

  def __init__(self, fat_date_time=None):
    """Initializes a FAT date time.

    Args:
      fat_date_time (Optional[int]): FAT date time.
    """
    number_of_seconds = None
    if fat_date_time is not None:
      number_of_seconds = self._GetNumberOfSeconds(fat_date_time)

    super(FATDateTime, self).__init__()
58
    self._precision = definitions.PRECISION_2_SECONDS
59
    self._number_of_seconds = number_of_seconds
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76

  def _GetNormalizedTimestamp(self):
    """Retrieves the normalized timestamp.

    Returns:
      decimal.Decimal: normalized timestamp, which contains the number of
          seconds since January 1, 1970 00:00:00 and a fraction of second used
          for increased precision, or None if the normalized timestamp cannot be
          determined.
    """
    if self._normalized_timestamp is None:
      if self._number_of_seconds is not None and self._number_of_seconds >= 0:
        self._normalized_timestamp = (
            decimal.Decimal(self._number_of_seconds) +
            self._FAT_DATE_TO_POSIX_BASE)

    return self._normalized_timestamp
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96

  def _GetNumberOfSeconds(self, fat_date_time):
    """Retrieves the number of seconds from a FAT date time.

    Args:
      fat_date_time (int): FAT date time.

    Returns:
      int: number of seconds since January 1, 1980 00:00:00.

    Raises:
      ValueError: if the month, day of month, hours, minutes or seconds
          value is out of bounds.
    """
    day_of_month = (fat_date_time & 0x1f)
    month = ((fat_date_time >> 5) & 0x0f)
    year = (fat_date_time >> 9) & 0x7f

    days_per_month = self._GetDaysPerMonth(year, month)
    if day_of_month < 1 or day_of_month > days_per_month:
97
      raise ValueError('Day of month value out of bounds.')
98 99 100 101 102 103 104 105 106 107 108 109 110

    number_of_days = self._GetDayOfYear(1980 + year, month, day_of_month)
    number_of_days -= 1
    for past_year in range(0, year):
      number_of_days += self._GetNumberOfDaysInYear(past_year)

    fat_date_time >>= 16

    seconds = (fat_date_time & 0x1f) * 2
    minutes = (fat_date_time >> 5) & 0x3f
    hours = (fat_date_time >> 11) & 0x1f

    if hours not in range(0, 24):
111
      raise ValueError('Hours value out of bounds.')
112 113

    if minutes not in range(0, 60):
114
      raise ValueError('Minutes value out of bounds.')
115 116

    if seconds not in range(0, 60):
117
      raise ValueError('Seconds value out of bounds.')
118 119

    number_of_seconds = (((hours * 60) + minutes) * 60) + seconds
120
    number_of_seconds += number_of_days * definitions.SECONDS_PER_DAY
121 122
    return number_of_seconds

123
  def CopyFromDateTimeString(self, time_string):
124
    """Copies a FAT date time from a date and time string.
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139

    Args:
      time_string (str): 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. The time of day, seconds
          fraction and time zone offset are optional. The default time zone
          is UTC.

    Raises:
      ValueError: if the time string is invalid or not supported.
    """
    date_time_values = self._CopyDateTimeFromString(time_string)

140 141 142 143 144 145
    year = date_time_values.get('year', 0)
    month = date_time_values.get('month', 0)
    day_of_month = date_time_values.get('day_of_month', 0)
    hours = date_time_values.get('hours', 0)
    minutes = date_time_values.get('minutes', 0)
    seconds = date_time_values.get('seconds', 0)
146 147

    if year < 1980 or year > (1980 + 0x7f):
148
      raise ValueError('Year value not supported: {0!s}.'.format(year))
149

150
    self._normalized_timestamp = None
151 152 153 154 155 156
    self._number_of_seconds = self._GetNumberOfSecondsFromElements(
        year, month, day_of_month, hours, minutes, seconds)
    self._number_of_seconds -= self._FAT_DATE_TO_POSIX_BASE

    self.is_local_time = False

157 158 159 160
  def CopyToDateTimeString(self):
    """Copies the FAT date time to a date and time string.

    Returns:
161 162
      str: date and time value formatted as: "YYYY-MM-DD hh:mm:ss" or None
          if number of seconds is missing.
163 164
    """
    if self._number_of_seconds is None:
165
      return None
166 167 168 169

    number_of_days, hours, minutes, seconds = self._GetTimeValues(
        self._number_of_seconds)

170 171
    year, month, day_of_month = self._GetDateValuesWithEpoch(
        number_of_days, self._EPOCH)
172 173 174 175

    return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}'.format(
        year, month, day_of_month, hours, minutes, seconds)

176 177
  def GetDate(self):
    """Retrieves the date represented by the date and time values.
178 179

    Returns:
180 181
       tuple[int, int, int]: year, month, day of month or (None, None, None)
           if the date and time values do not represent a date.
182
    """
183 184 185 186 187 188
    if self._number_of_seconds is None:
      return None, None, None

    try:
      number_of_days, _, _, _ = self._GetTimeValues(self._number_of_seconds)
      return self._GetDateValuesWithEpoch(number_of_days, self._EPOCH)
189

190 191
    except ValueError:
      return None, None, None