1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
|
# Copyright (C) 2015-2019 Chris Lalancette <clalancette@gmail.com>
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation;
# version 2.1 of the License.
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
'''
Classes and utilities for ISO date support.
'''
from __future__ import absolute_import
import struct
import time
try:
from functools import lru_cache
except ImportError:
from pycdlib.backport_functools import lru_cache # type: ignore
from pycdlib import pycdlibexception
from pycdlib import utils
@lru_cache(maxsize=256)
def string_to_timestruct(input_string):
# type: (bytes) -> time.struct_time
'''
A cacheable function to take an input string and decode it into a
time.struct_time from the time module. If the string cannot be decoded
because of an illegal value, then the all-zero time.struct_time will be
returned instead.
Parameters:
input_string - The string to attempt to parse.
Returns:
A time.struct_time object representing the time.
'''
try:
timestruct = time.strptime(input_string.decode('utf-8'), VolumeDescriptorDate.TIME_FMT)
except ValueError:
# Ecma-119, 8.4.26.1 specifies that if the string was all the digit
# zero, with the last byte 0, the time wasn't specified. In that
# case, time.strptime() with our format will raise a ValueError.
# In practice we have found that some ISOs specify various wacky
# things in this field, so if we see *any* ValueError, we just
# assume the date is unspecified and go with that.
timestruct = time.struct_time((0, 0, 0, 0, 0, 0, 0, 0, 0))
return timestruct
class DirectoryRecordDate(object):
'''
A class to represent a Directory Record date as described in Ecma-119
section 9.1.5. The Directory Record date consists of the number of years
since 1900, the month, the day of the month, the hour, the minute, the
second, and the offset from GMT in 15 minute intervals. There are two main
ways to use this class: either to instantiate and then parse a string to
fill in the fields (the parse() method), or to create a new entry with a
tm structure (the new() method).
'''
FMT = '=BBBBBBb'
__slots__ = ('_initialized', 'years_since_1900', 'month', 'day_of_month',
'hour', 'minute', 'second', 'gmtoffset')
def __init__(self):
# type: () -> None
self._initialized = False
def parse(self, datestr):
# type: (bytes) -> None
'''
Parse a Directory Record date out of a string.
Parameters:
datestr - The string to parse the date out of.
Returns:
Nothing.
'''
if self._initialized:
raise pycdlibexception.PyCdlibInternalError('Directory Record Date already initialized')
(self.years_since_1900, self.month, self.day_of_month, self.hour,
self.minute, self.second,
self.gmtoffset) = struct.unpack_from(self.FMT, datestr, 0)
self._initialized = True
def new(self):
# type: () -> None
'''
Create a new Directory Record date based on the current time.
Parameters:
tm - An optional argument that must be None
Returns:
Nothing.
'''
if self._initialized:
raise pycdlibexception.PyCdlibInternalError('Directory Record Date already initialized')
# This algorithm was ported from cdrkit, genisoimage.c:iso9660_date()
tm = time.time()
local = time.localtime(tm)
self.years_since_1900 = local.tm_year - 1900
self.month = local.tm_mon
self.day_of_month = local.tm_mday
self.hour = local.tm_hour
self.minute = local.tm_min
self.second = local.tm_sec
self.gmtoffset = utils.gmtoffset_from_tm(tm, local)
self._initialized = True
def record(self):
# type: () -> bytes
'''
Return a string representation of the Directory Record date.
Parameters:
None.
Returns:
A string representing this Directory Record Date.
'''
if not self._initialized:
raise pycdlibexception.PyCdlibInternalError('Directory Record Date not initialized')
return struct.pack(self.FMT, self.years_since_1900, self.month,
self.day_of_month, self.hour, self.minute,
self.second, self.gmtoffset)
def __ne__(self, other):
return self.years_since_1900 != other.years_since_1900 or self.month != other.month or self.day_of_month != other.day_of_month or self.hour != other.hour or self.minute != other.minute or self.second != other.second or self.gmtoffset != other.gmtoffset
class VolumeDescriptorDate(object):
'''
A class to represent a Volume Descriptor Date as described in Ecma-119
section 8.4.26.1. The Volume Descriptor Date consists of a year (from 1 to
9999), month (from 1 to 12), day of month (from 1 to 31), hour (from 0
to 23), minute (from 0 to 59), second (from 0 to 59), hundredths of second,
and offset from GMT in 15-minute intervals (from -48 to +52) fields. There
are two main ways to use this class: either to instantiate and then parse a
string to fill in the fields (the parse() method), or to create a new entry
with a tm structure (the new() method).
'''
TIME_FMT = '%Y%m%d%H%M%S'
EMPTY_STRING = b'0' * 16 + b'\x00'
__slots__ = ('_initialized', 'year', 'month', 'dayofmonth', 'hour',
'minute', 'second', 'hundredthsofsecond', 'gmtoffset',
'date_str')
def __init__(self):
# type: () -> None
self._initialized = False
def parse(self, datestr):
# type: (bytes) -> None
'''
Parse a Volume Descriptor Date out of a string. A string of all zeros
is valid, which means that the date in this field was not specified.
Parameters:
datestr - string to be parsed
Returns:
Nothing.
'''
if self._initialized:
raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor Date object is already initialized')
if len(datestr) != 17:
raise pycdlibexception.PyCdlibInvalidISO('Invalid ISO9660 date string')
timestruct = string_to_timestruct(datestr[:-3])
self.year = timestruct.tm_year
self.month = timestruct.tm_mon
self.dayofmonth = timestruct.tm_mday
self.hour = timestruct.tm_hour
self.minute = timestruct.tm_min
self.second = timestruct.tm_sec
if timestruct.tm_year == 0 and timestruct.tm_mon == 0 and timestruct.tm_mday == 0 and timestruct.tm_hour == 0 and timestruct.tm_min == 0 and timestruct.tm_sec == 0:
self.hundredthsofsecond = 0
self.gmtoffset = 0
self.date_str = self.EMPTY_STRING
else:
try:
self.hundredthsofsecond = int(datestr[14:16])
except ValueError:
# We've seen ISOs in the wild (made by MagicISO) that fill
# hundredthsofseconds with b'\x00\x00'. Handle that here.
self.hundredthsofsecond, = struct.unpack('>H', datestr[14:16])
self.gmtoffset, = struct.unpack_from('=b', datestr, 16)
self.date_str = datestr
self._initialized = True
def record(self):
# type: () -> bytes
'''
Return the date string for this object.
Parameters:
None.
Returns:
Date as a string.
'''
if not self._initialized:
raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor Date is not initialized')
return self.date_str
def new(self, tm=0.0):
# type: (float) -> None
'''
Create a new Volume Descriptor Date. If tm is None, then this Volume
Descriptor Date will be full of zeros (meaning not specified). If tm
is not None, it is expected to be a struct_time object, at which point
this Volume Descriptor Date object will be filled in with data from that
struct_time.
Parameters:
tm - struct_time object to base new VolumeDescriptorDate off of,
or 0.0 for an empty VolumeDescriptorDate.
Returns:
Nothing.
'''
if self._initialized:
raise pycdlibexception.PyCdlibInternalError('This Volume Descriptor Date object is already initialized')
if tm != 0.0:
local = time.localtime(tm)
self.year = local.tm_year
self.month = local.tm_mon
self.dayofmonth = local.tm_mday
self.hour = local.tm_hour
self.minute = local.tm_min
self.second = local.tm_sec
self.hundredthsofsecond = 0
self.gmtoffset = utils.gmtoffset_from_tm(tm, local)
self.date_str = time.strftime(self.TIME_FMT, local).encode('utf-8') + '{:0<2}'.format(self.hundredthsofsecond).encode('utf-8') + struct.pack('=b', self.gmtoffset)
else:
self.year = 0
self.month = 0
self.dayofmonth = 0
self.hour = 0
self.minute = 0
self.second = 0
self.hundredthsofsecond = 0
self.gmtoffset = 0
self.date_str = self.EMPTY_STRING
self._initialized = True
def __ne__(self, other):
return self.year != other.year or self.month != other.month or self.dayofmonth != other.dayofmonth or self.hour != other.hour or self.minute != other.minute or self.second != other.second or self.hundredthsofsecond != other.hundredthsofsecond or self.gmtoffset != other.gmtoffset or self.date_str != other.date_str
|