How do I parse MS-DOS time in perl? - perl

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://www.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
};

Related

How create hash with arrays in perl

I have a script that looped over to get the values ​​$mac and $snr. The result of the loop is as follows
133.3133.5132.9133.1132.9131.8234.4233.7234.7230.3232.3230.8331.9332.0331.6331.6330.9330.2 itd.....
9437.39437.09436.79436.89435.19433.09535.29535.
result from $mac is bolt
result form $snr is next
how create %hash where result will be
my %hash = ( mac1 => [’snr1', ‘snr2', ‘snr3', ‘snr4', ‘snr5', ‘snr6'],
mac2 => [’snr1', ‘snr2', ‘snr3', ‘snr4', ‘snr5', ‘snr6'],
mac3 => [’snr1', ‘snr2', ‘snr3', ‘snr4', ‘snr5', ‘snr6'],
);
result with value
my %hash = ( 1 => [’33.3', ‘33.5', ‘32.9', ‘33.1', ‘32.9','31.8' ‘'],
2 => [’34.4', ‘33.7', ‘34.7', ‘30.3', ‘32.3', ‘30.8'],
3 => [’31.9', ‘32.0', ‘31.6', ‘31.6', ‘snr5', ‘snr6'],
);
I can put only part code:
I can put only part code:
my $db_snr = DBI->connect("DBI:mysql:$baza:$host", $user, $pass, { RaiseError=>'0', AutoCommit=>'1'} );
if(! $db_snr){
print LOG "Can't connect to mysql serwer at $host, skipping!\n";
exit 0;
}
foreach $upstrim (sort keys %interface){
$typ = $interface{$upstrim}[50];
$mac=$interface{$upstrim}[0];
$mac=~ s/MAC//;
print($snr)
print($mac) # here I woul like create hash witch array
if(($typ eq 'u') && ($mac =~ m/^\d/)){
my $snr = $interface{$upstrim}[30];
if (($snr < 11) && ($snr > 0)) {
print LOG "Test SNR nadajcy $cmts:$mac:$snr:$typ\n";
$db_snr->do("insert into snr values ('$cmts','$mac',NOW(),NOW(),'$snr','1','0','$upstrim') ON DUPLICATE KEY update snr='$snr' ,data_last=NOW(), licznik=licznik+1 ") or warn "Can't prepare: $DBI::errstr";
}
}
}
$db_snr->disconnect;
close(LOG);
You can just loop over the values, split the line, then use a push statement to add numbers to the hash value, like below. Note that I am using the infile handle DATA for simplicity here, you have to use your own file handle, or the <> operator.
use strict;
use warnings;
use Data::Dumper;
my %hash;
while (<DATA>) {
my ($mac, $snr) = split;
push #{ $hash{$mac} }, $snr;
}
print Dumper \%hash;
__DATA__
1 33,3
1 33.2
1 33.3
1 32.7
1 32.9
1 32.5
1 31.7
2 34.4
2 34.9
2 34.6
2 34.3
2 33.5
2 30.8
3 31.9
3 32.0
3 31.8
3 31.7
3 31.4
3 30.4
95 34.8
96 30.6
96 31.8
96 33.4
96 34.2
96 34.0
96 29.5
Output:
$VAR1 = {
'3' => [
'31.9',
'32.0',
'31.8',
'31.7',
'31.4',
'30.4'
],
'1' => [
'33,3',
'33.2',
'33.3',
'32.7',
'32.9',
'32.5',
'31.7'
],
'2' => [
'34.4',
'34.9',
'34.6',
'34.3',
'33.5',
'30.8'
],
'96' => [
'30.6',
'31.8',
'33.4',
'34.2',
'34.0',
'29.5'
],
'95' => [
'34.8'
]
};

Print size and count in adjacent rather than in new line in Perl

I have written a script which will fetch values from db and store it in the hash called total_hash. To make question simpler I have hard coded the hash here.
Next step I need to display these hash content in table format, so used a below logic to display it on the screen.
#!/usr/bin/perl
use strict; use warnings;
use Data::Dumper;
my $method_total = 1;
my $method_a = 1;
my $method_b = 1;
my $method_sync = 1;
my %total_hash = (
'Method_Combine' => {
'flag' => 1,
'count' => 27,
'name' => '| Method TOTAL',
'size' => 270
},
'Method_A' => {
'count' => 7,
'flag' => 1,
'name' => '| Method A',
'size' => 70
},
'Method_B' => {
'count' => 20,
'flag' => 1,
'size' => 200,
'name' => '| Method B'
},
'Method_Sync' => {
'name' => '| Method Sync',
'size' => 300,
'flag' => 1,
'count' => 30
}
);
#print "total_hash:\n".Dumper(\%total_hash);
printReport(\%total_hash);
sub printReport {
my $hash = shift;
my $string1 = "";
my $string2 = "";
my $string3 = "";
my $append_data = "";
my $length = 20;
my $total_length = $length * ($method_sync + $method_a + $method_b + $method_total) + 1;
my #columns = ('Method_Sync','Method_A','Method_B','Method_Combine');
foreach my $key (#columns) {
if ($hash->{$key}->{flag}) {
$append_data = sprintf("%-".$length."s",$hash->{$key}->{name});
$string1 .= $append_data;
$append_data = sprintf("%-".$length."s","| ".$hash->{$key}->{size}." Bytes");
$string2 .= $append_data;
$append_data = sprintf("%-".$length."s","| ".$hash->{$key}->{count});
$string3 .= $append_data;
}
}
print("Report:\n\n");
print "-" x $total_length."\n";
print $string1 . "|\n";
print "-" x $total_length."\n";
print $string2 . "|\n";
print "-" x $total_length."\n";
print $string3 . "|\n";
print "-" x $total_length."\n";
}
Here content is getting printing like below (actual ouput):
---------------------------------------------------------------------------------
| Method Sync | Method A | Method B | Method TOTAL |
---------------------------------------------------------------------------------
| 300 Bytes | 70 Bytes | 200 Bytes | 270 Bytes |
---------------------------------------------------------------------------------
| 30 | 7 | 20 | 10 |
---------------------------------------------------------------------------------
I want it to be printed like below (expected result):
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
| Method Sync | Method Sync Count | Method A | Method A Count | Method B | Method B Count | Method TOTAL | Method TOTAL Count|
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
| 300 Bytes | 30 | 70 Bytes | 7 | 200 Bytes | 20 | 270 Bytes | 27 |
---------------------------------------------------------------------------------------------------------------------------------------------------------------
Each of the Methods Size and Count should be printed adjacent side rather than printing in the new line for the count. How to do that?
Here is one way to format it for display as desired.
To align the key names with their size/count prepare columns with name, value(s), and width (maximum between the name and the corresponding value). Then lay them out: first the line with headers then line(s) with their corresponding data, all using their column's width. Add separators per taste (see comment on that at the end)
use warnings;
use strict;
use feature 'say';
#use Data::Dump qw(dd);
use List::Util qw(max);
my %total_hash = (
'Method_Combine' =>
{ 'count' => 27, 'name' => 'Method TOTAL', 'size' => 270 },
'Method_A' =>
{ 'count' => 7, 'name' => 'Method A', 'size' => 70 },
'Method_B' =>
{ 'count' => 20, 'name' => 'Method B', 'size' => 200 },
'Method_Sync' =>
{ 'count' => 30, 'name' => 'Method Sync', 'size' => 300 }
);
my #keys = qw(Method_Sync Method_A Method_B Method_Combine); # ordered
my #cols;
for (#keys) {
push #cols, {
header => $total_hash{$_}{name},
data => $total_hash{$_}{size} . ' Bytes',
width => max map { length }
($total_hash{$_}{name}, $total_hash{$_}{size} . ' Bytes')
};
push #cols, {
header => $total_hash{$_}{name} . ' ' . 'Count',
data => $total_hash{$_}{count},
width => max map { length }
($total_hash{$_}{name} . ' ' . 'Count', $total_hash{$_}{count})
};
}
# dd \#cols; # just to see it
my $hdr_str = '| ' . join(' | ',
map { sprintf "%-*s", $_->{width}, $_->{header} } #cols) . ' |';
my $dat_str = '| ' . join(' | ',
map { sprintf "%-*s", $_->{width}, $_->{data} } #cols) . ' |';
my $sep = '-' x length $hdr_str;
say for $sep, $hdr_str, $sep, $dat_str, $sep;
Prints
--------------------------------------------------------------------------------------------------------------------------------
| Method Sync | Method Sync Count | Method A | Method A Count | Method B | Method B Count | Method TOTAL | Method TOTAL Count |
--------------------------------------------------------------------------------------------------------------------------------
| 300 Bytes | 30 | 70 Bytes | 7 | 200 Bytes | 20 | 270 Bytes | 27 |
--------------------------------------------------------------------------------------------------------------------------------
I've changed the hash and requirements a little
I formatted columns to be each as wide as needed, not all the same. That only takes more work and if you want them all to be the same width it's easier: find the maximum width of all names and values and use that everywhere. No need for width element then
I've removed the purely formatting part (| ) of the string which is the value of name. While I can see the rationale for having it there, it is generally better to separate data from format; those bars are easily added as needed (or not, or something else can be)
I've also removed a key/value which is unused here (flag => 1), for easier viewing
There are of course libraries for this, once we've prepared header and data columns, like text-templates and various modules for formatted/tabular print of text.† Then there is also Perl's own format, and better yet Perl6::Form. But sprintf is adequate here.
A note on style. Experience shows that extra formatting elements in tables generally end up distracting rather than helping. It is usually enough to separate a header, and have sufficient spacing between columns. Other than that, less is more for clarity. Perhaps consider
Method Sync Method Sync Count Method A Method A Count
---------------------------------------------------------------- ...
300 Bytes 30 70 Bytes 7
† Some libraries: Text::Table, Text::TabularDisplay, Text::SimpleTable::Autowidth

Minimum and maximum values for Perl hash of hashes

This is a variation from another question asked on perlmonks and is similar to the problem I'm trying to figure out. I have the following hash of hashes.
%Year = (
2007 => {
ID1 => 07,
ID4 => 34,
ID2 => 24,
ID9 => 14,
ID3 => 05,
},
2008 => {
ID7 => 11,
ID9 => 64,
ID10 => 20,
ID5 => 13,
ID8 => 22,
}
)
I would like to find the two smallest and two largest values together with their corresponding IDs for each year. Can this be done using List::Util qw (min max)?
Desired results:
2007 - max1:ID4,34 max2:ID2,24 min1:ID3,05 min2:ID1,07
2008 - max1:ID9,64 max2:ID10,20 min1:ID7,11 min2:ID5,13
Unless the lists are huge, it is probably best to find the two largest and two smallest hash values just by sorting the entire hash and picking the first two and last two elements.
You seem to have incorrect expectations for your output. For 2008 the hash data sorted by value looks like
ID7 => 11
ID5 => 13
ID10 => 20
ID8 => 22
ID9 => 64
so max1 and max2 are ID9 and ID8, while min1 and min2 are are ID7 and ID5. But your question says that you expect max2 to be ID10, whose value is 20 - right in the middle of the sorted range. I think max2 should be ID8 which has a value of 22 - the second largest value in the 2008 hash.
I suggest this solution to produce the output that I think you want
use strict;
use warnings;
use 5.010;
my %year = (
2007 => { ID1 => 7, ID2 => 24, ID3 => 5, ID4 => 34, ID9 => 14 },
2008 => { ID10 => 20, ID5 => 13, ID7 => 11, ID8 => 22, ID9 => 64 },
);
for my $year (sort { $a <=> $b } keys %year) {
my $data = $year{$year};
my #sorted_keys = sort { $data->{$a} <=> $data->{$b} } keys %$data;
printf "%4d - max1:%s,%02d max2:%s,%02d min1:%s,%02d min2:%s,%02d\n",
$year, map { $_ => $data->{$_} } #sorted_keys[-1,-2,0,1];
}
output
2007 - max1:ID4,34 max2:ID2,24 min1:ID3,05 min2:ID1,07
2008 - max1:ID9,64 max2:ID8,22 min1:ID7,11 min2:ID5,13
TIMTOWDI: You've mentioned hash of hash, so you can sort your inner hash by values and take a slice (that is first two and last two elements).
#!/usr/bin/perl
use strict;
use warnings;
my %Year = (
2007 => { ID1 => 7, ID2 => 24, ID3 => 5, ID4 => 34, ID9 => 14 },
2008 => { ID10 => 20, ID5 => 13, ID7 => 11, ID8 => 22, ID9 => 64 },
);
for my $year (keys %Year) {
printf "%4d - max1:%s,%02d max2:%s,%02d min1:%s,%02d min2:%s,%02d\n",
$year,
map { $_, $Year{$year}{$_} }
( sort { $Year{$year}{$b} <=> $Year{$year}{$a} } keys %{$Year{$year}} )[0,1,-1,-2];
}
Output:
2007 - max1:ID4,34 max2:ID2,24 min1:ID3,05 min2:ID1,07
2008 - max1:ID9,64 max2:ID8,22 min1:ID7,11 min2:ID5,13
You have hashes, and List::Util works on lists/arrays. That disqualifies you right there since both the keys and the data are still important for you.
It's possible to create a second hash that's keyed by the data, then I could use something from List::Util or List::MoreUtils on that to pull up the data you want, and then look up the keys for that data. However, that's a lot of work just to get the information you want.
In reality, you're not sorting the hash of hashes, but just the data in each year. This makes the job a lot easier.
Normally, when you sort a hash, you're sorting on the keys. However, you can specify a subroutine inside the sort command to change the way Perl sorts. Perl will hand you two items $a and $b which represents the keys to your hash. You figure out which is the bigger one, and pass that back to Perl. Perl gives you <=> for numbers and cmp for non-numeric data.
All I have to do is specify sort { $array{$a} cmp $array{$b} } keys %array to sort by the data and not the keys. I simply toss the sorted keys into another array, then use index positioning to pull out the data I want.
#! /usr/bin/env perl
use warnings;
use strict;
use autodie;
use feature qw(say);
use Data::Dumper;
my %year;
#
# Data
#
$year{2007}->{ID1} = "07";
$year{2007}->{ID2} = "24";
$year{2007}->{ID3} = "05";
$year{2007}->{ID4} = "34";
$year{2007}->{ID9} = "14";
$year{2008}->{ID7} = "11";
$year{2008}->{ID9} = "64";
$year{2008}->{ID10} = "20";
$year{2008}->{ID5} = "13";
$year{2008}->{ID8} = "22";
#
# For Each Year...
#
for my $year ( sort keys %year ) {
print "$year - ";
#
# No need to do this dereferencing, but it makes the rest of the code cleaner
#
my %id_hash = %{ $year{$year} };
#
# Now I sort my IDs by their data and not the key names
#
my #keys = sort { $id_hash{$a} cmp $id_hash{$b} } keys %id_hash;
#
# And print them out
#
print "max1:$keys[-1],$id_hash{$keys[-1]} ";
print "max2:$keys[-2],$id_hash{$keys[-2]} ";
print "min1:$keys[0],$id_hash{$keys[0]}, ";
print "min2:$keys[1],$id_hash{$keys[1]}\n";
}
The output is:
2007 - max1:ID4,34 max2:ID2,24 min1:ID3,05, min2:ID1,07
2008 - max1:ID9,64 max2:ID8,22 min1:ID7,11, min2:ID5,13

Storing values corresponding to an unique key in perl

I am new to perl. I want to store the values corresponding to the keys in the following fashion. Please see below for a sample input data. Could someone help me to do this in perl.
output:
key value
1 (11, 20)
2 (17, 15)
3 (10, 11)
Input data:
key value
2 17
3 10
1 11
1 20
2 15
3 11
You can store the data in a hash-of-arrays structure (perldoc perldsc):
use warnings;
use strict;
use Data::Dumper;
$Data::Dumper::Sortkeys = 1;
my %data;
while (<DATA>) {
my ($k, $v) = split;
push #{ $data{$k} } , $v;
}
print Dumper(\%data);
=for output
$VAR1 = {
'1' => [
'11',
'20'
],
'2' => [
'17',
'15'
],
'3' => [
'10',
'11'
]
};
=cut
__DATA__
2 17
3 10
1 11
1 20
2 15
3 11
Assuming that each of the lines in the input data is in
a string, use a hash which has unique keys by definition.
If the key exists in the hash push the value onto the arrayref.
If it doesn't exist assign an array reference to the key with the
value. Next time that key appears you'll push the value onto the arrayref.
my $hash = {};
foreach my $line ( #lines ) {
my ($key, $val) = split(/\s/, $line);
if( $hash->{$key} ) {
push( #{ $hash->{ $key } }, $val );
}
else {
$hash->{$key} = [ $val ];
}
}
Using oneliner:
$ echo "key value
2 17
3 10
1 11
1 20
2 15
3 11" | perl -anE'next if 1..1;push#{$h{$F[0]}},$F[1]}{say"key value";$"=", ";say"$_ (#{$h{$_}})"for sort{$a<=>$b}keys%h'
key value
1 (11, 20)
2 (17, 15)
3 (10, 11)

Compute NUM of days from UNION of time ranges (with DateTime::Span)

I have a set of time ranges and need to get the Number of days from this set. I did it with DateTime::Span as follows:
use DateTime;
use DateTime::Span;
sub printHash {
my $hash = shift;
foreach my $key (keys %{$hash}) {
print "$key: $hash->{$key}\n";
}
}
$date1 = DateTime->new( year => 2002, month => 3, day => 11 );
$date2 = DateTime->new( year => 2002, month => 4, day => 12 );
$date3 = DateTime->new( year => 2003, month => 8, day => 26 );
$date4 = DateTime->new( year => 2003, month => 9, day => 15 );
$date5 = DateTime->new( year => 2004, month => 7, day => 6 );
$date6 = DateTime->new( year => 2004, month => 10, day => 15 );
$set1 = DateTime::Span->from_datetimes( start => $date1, end => $date2 );
$set2 = DateTime::Span->from_datetimes( start => $date3, end => $date4 );
$set3 = DateTime::Span->from_datetimes( start => $date5, end => $date6 );
$set123 = $set1->union( $set2, $set3 );
printHash(\%$set123);
#--------------------------------
set123:
set: [2002-03-11T00:00:00..2002-04-12T00:00:00],[2003-08-26T00:00:00..2003-09
T00:00:00],[2004-07-06T00:00:00..2004-10-15T00:00:00]
Is there a way to extract the Number of days in this $set123? Thanks a lot!
Update: I can do it with delta_days but it's actually I do need a UNION of the time ranges then find the Number of days within this UNION set. I find DateTime::Span works well to find the UNION but I just need to know how to extract the Number of days in it. Since I need a solution in a hurry, so please help! Thanks!
First, you're calling union incorrectly. It only takes one argument.
$set1->union($set2)->union($set3);
Secondly, the result of a union is actually a DateTime::SpanSet, not a DateTime::Span.
my $spanset = $set1->union($set2)->union($set3);
Finally, both ::Span and ::SpanSet provide a method called duration which return a DateTime::Duration object.
my $dur = $spanset->duration();
Unfortunately, it doesn't return the result as days, and it's impossible to convert what it does return into days. That means you need to iterate over the spans that make up the span set, and sum the size of those.
my $days = 0;
my $iter = $spanset->iterator();
while ( my $span = $iter->next ) {
my $date1 = $span->start;
my $date2 = $span->end;
$days += $date2->delta_days($date1)->in_units('days');
}
print("$days\n"); # 153
Actually, you don't need to use DateTime::Span. The delta_days method of DateTime does the trick nicely:
use strict;
use warnings;
use DateTime;
my $dt1=DateTime->new(year=>2002,month=>3,day=>11,time_zone=>"local");
my $dt2=DateTime->new(year=>2002,month=>4,day=>11,time_zone=>"local");
print $dt2->delta_days($dt1)->in_units("days") . "\n"; #31