开发者

How do I parse MS-DOS time in perl?

开发者 https://www.devze.com 2023-03-30 00:39 出处:网络
I\'m reading a binary file using perl. In the file\'s headers, there\'s 4 bytes that represents MS-DOS time. How do I read this time? I\'m not familiar with this format.

I'm reading a binary file using perl. In the file's headers, there's 4 bytes that represents MS-DOS time. How do I read this time? I'm not familiar with this format.

I've found this for reference: http://w开发者_如何学JAVAww.vsft.com/hal/dostime.htm but I'm still not sure how to read it.


Another approach:

sub mst {
  my $msdos_time = shift;
  my @t = map { ord }
          map { pack("b*", $_) }
          map { reverse($_) }
          unpack("A5 A6 A5 A5 A4 A7", unpack("b*", $msdos_time));
  my %d;
  @d{seconds,minutes,hours,day,month,year} = @t;
  $d{seconds} *= 2;
  $d{year} += 1980;
  return \%d;
}

This will work if $msdos_time is represented in little-endian format which (I believe) is how it would be laid out in memory.

(Clearly the chained map-s could be coalesced - I wrote it this way to make it easier to see what was going on.)

Example:

print Dumper(mst("\x22\x12\x01\x41"));

# byte 0   | byte 1   | byte 2   | byte 3
# 76543210 | 76543210 | 76543210 | 76543210
#    S...s                                   seconds
# ..m             M..                        minutes
#            H...h                           hours
#                          D...d             day
#                       ..m               M  month
#                                  Y.....y   year
# 00100010 | 00010010 | 00000001 | 01000001

$VAR1 = {
      'seconds' => 4,
      'hours' => 2,
      'month' => 8,
      'day' => 1,
      'minutes' => 17,
      'year' => 2012
    };


You can't use pack because it always wants to start on a byte boundary. Some of these values go across byte boundaries too, so you don't want to deal with individual bytes (although words would work). It's easier to just to mask and shift.

In this example, I set up the masks so I don't have to think too hard about it, then use those to grab the values out of the string. I don't really know anything about the DOS time format, but from what I've read, you have to multiply the seconds by 2 (notice it's only five bits):

use 5.010;
use strict;
use warnings;

use Data::Dumper;

# seconds   minutes   hours    day    month   years from 1980
#  5 bits    6         5        5       4       7
my $datetime = 0b11011_000011_11111_01100_1011_0001000;

my $parsed = parse( $datetime );
print Dumper( $parsed );

sub parse {
    my( $datetime ) = @_;
    state $masks = make_masks();

    my %this = map {
        $_, ( $datetime & $masks->{$_}[0] ) >> $masks->{$_}[1]
        } keys %$masks;

    $this{seconds} *= 2;
    $this{years} += 1980;

    return \%this;
    }

sub make_masks {
    my %masks = (
        seconds => [ 0b11111,  27 ],
        minutes => [ 0b111111, 21 ],
        hours   => [ 0b11111,  16 ],
        day     => [ 0b11111,  11 ],
        month   => [ 0b1111,    7 ],
        years   => [ 0b1111111, 0 ],
        );  

    foreach my $key ( sort { $masks{$a}[1] <=> $masks{$b}[1] } keys %masks ) {
        $masks{$key}[0] <<= $masks{$key}[1];
        }

    return \%masks;
    }

My output is just a hash:

$VAR1 = {
          'seconds' => 54,
          'hours' => 31,
          'years' => 1988,
          'month' => 11,
          'minutes' => 3,
          'day' => 12
        };
0

精彩评论

暂无评论...
验证码 换一张
取 消