Parse date String to milliseconds and vice versa - perl

I have following date in string format and I need to convert it to milliseconds using a Perl script. I have tried to convert it using DateTime::Format::Strptime and it returns 0 after convert to millisecond.
Date String : 01-13-15:14.16
#!/usr/bin/perl
use DateTime::Format::Strptime;
my $strp = DateTime::Format::Strptime->new(
pattern => '%m-%d-%y:%H.%M',
on_error => 'croak',
);
my $dt = $strp->parse_datetime("01-13-15:14.16");
print $dt->millisecond."\n";
How can I convert datetime to milliseconds

I think it's likely that what you want is not milliseconds but epoch, which is the number of seconds since January 1 1970. Your date-time format has neither seconds nor milliseconds, so both of these fields will be reported as zero, and if you really want milliseconds then you can simply multiply the epoch by 1000.
It looks like this
use strict;
use warnings;
use 5.010;
use DateTime::Format::Strptime;
my $strp = DateTime::Format::Strptime->new(
pattern => '%m-%d-%y:%H.%M',
on_error => 'croak',
);
my $dt = $strp->parse_datetime('01-13-15:14.16');
say $dt->epoch;
output
1421158560
However, the DateTime module is enormous and slow, and there is a better solution in the core module Time::Piece, which would look like this
use strict;
use warnings;
use 5.010;
use Time::Piece;
my $dt = Time::Piece->strptime('01-13-15:14.16', '%m-%d-%y:%H.%M');
say $dt->epoch;
output
1421158560

It is impossible to convert a date to milliseconds. A date is a point in time. Milliseconds measure a duration.
The only way your question makes sense is if we assume you mean "how many milliseconds are there between my given date/time and some other fixed date/time". Because of the way that Unix measures time, let's assume that the fixed date/time you're measuring from is the Unix Epoch (1970-01-01 00:00:00).
You get the number of seconds since the epoch from a DateTime object with the epoch() method.
say $dt->epoch;
To get the number of milliseconds, just multiply that by a thousand.
say 1000 * $dt->epoch;
Your current timestamp doesn't include milliseconds. If you change to parsing strings that include them, then you can add that value on too.
say $dt->milliseconds + (1000 * $dt->epoch);

Adding my comment as an answer:
If you want to convert the datetime value into seconds since the epoch, try one of these:
print sprintf("%f", $dt->epoch + ($dt->millisecond/1000));
print sprintf("%s.%s", $dt->epoch, $dt->millisecond);
(My original comment did not account for millisecond being an integer)
Similar for the values in milliseconds, rather than seconds.millis:
print sprintf("%d", ($dt->epoch * 1000) + $dt->millisecond);
print sprintf("%d%04d", $dt->epoch, $dt->millisecond);
Remember, that if you don't supply DateTime with a value containing milliseconds (or higher resolution), the value will simply be zero.

Related

Subtract days from today date and compare after

Following situation.
I have a file named 2018_12_03_FileName.log. Now I get the date from the file (2018_12_03).
I want to convert the string to a DateTime object, which works too.
$chars =~s/_//g;
$chars = Time::Piece->strptime("$chars", "%Y%m%d");
$chars = $chars->strftime("%d/%m/%Y");
Output
03/12/2018
After that I want to get the date today - 14 days. But here is one of my two problems. I tried many things, but couldn't find any real solution working for me.
my $day14 = DateTime->now();
$day14 -= (2 * ONE_WEEK);
Error:
Cannot subtract 1209600 from a DateTime object (DateTime=HASH(0x6f2d84)). Only a DateTime::Duration or DateTime object can be subtracted from a DateTime object.
Now the second problem is, I want to compare these two dates and look if the file date is in range or not.
my $cmp = DateTime->compare($chars, $day14);
Error:
Argument "15/07/2019" isn't numeric in numeric eq (==) at
A DateTime object can only be compared to another DateTime object (03/12/2018, 15/07/2019).
So how can I subtract 14 days from the today date and how can I compare these two dates after?
You're slightly muddling up two Date/Time ecosystems that don't work well together.
You can do this using Time::Piece and Time::Seconds.
use feature 'say';
use Time::Piece;
use Time::Seconds;
my $chars = '2018_12_03';
my $tp = Time::Piece->strptime($chars, '%Y_%m_%d');
my $date14 = $tp - (2 * ONE_WEEK);
say $tp->strftime('%d/%m/%Y');
say $date14->strftime('%d/%m/%Y');
Output:
03/12/2018
19/11/2018
Or you can do it using DateTime and friends.
use feature 'say';
use DateTime;
use DateTime::Format::Strptime;
my $date_parser = DateTime::Format::Strptime->new(
pattern => '%Y_%m_%d',
on_error => 'croak',
);
my $chars = '2018_12_03';
my $dt = $date_parser->parse_datetime($chars);
my $date14 = $dt->clone->subtract( weeks => 2 );
say $dt->strftime('%d/%m/%Y');
say $date14->strftime('%d/%m/%Y');
Output:
03/12/2018
19/11/2018
As for your last question, you can compare either Time::Piece objects or DateTime objects using the standard Perl comparison operators (<, ==, >=, etc). But you have to compare two objects of the same type.
Just another option, Time::Moment makes date math really simple:
use strict;
use warnings;
use Time::Moment;
my $chars = Time::Moment->from_object(Time::Piece->strptime("$chars", "%Y%m%d"));
my $day14 = Time::Moment->now_utc->minus_days(14)->at_midnight;
print $day14->strftime('%d/%m/%Y');
These objects can be compared with normal operators like with DateTime and Time::Piece. Just keep in mind that, since you only care about the day and not time of day, all math should be done according to the same time zone, of which the easiest is of course UTC.

Subtract two date strings in Perl with conversion to unix time and reverting back

I want to subtract two timestamps in Perl. I converted them to unix-time via the function below and convert the unix timestamp back to how it was. In the example below the result is 01:20:00 instead of 00:20:00
(I think it has sth to do with the start of the unix timestamp 1.1.1970 01:00:00 but not sure how to resolve it)
Any idea? Many thanks for your help in advance.
use POSIX qw( strftime );
use Time::Local qw( timelocal );
sub to_epoch {
$_ = shift;
my #a = split /\W+/, $_;
my $b = timelocal($a[5],$a[4],$a[3],$a[2],$a[1],$a[0]);
return $b;
}
my $h_end = "2018.11.12 00:50:00";
my $h_start = "2018.11.12 00:30:00";
my $duration = to_epoch($h_end) - to_epoch($h_start);
my $convert_back = POSIX::strftime("%H:%M:%S", localtime($duration));
print $convert_back , "\n";
Ouptut: 01:20:00
It works for me. But I think that's because I'm in GMT and you're in CET (GMT+1).
The flaw is in your final step. You are confusing two concepts - a point in time and a duration.
You correctly convert your two points in time to Unix epoch numbers and then you subtract those numbers to get the number of seconds between them. That number is a duration. And you want to convert that duration into a human-readable format. Using localtime() and POSIX::strtime() is not the way to do that. POSIX::strftime() and localtime() deal with points in time, not durations.
The number you get is 1,200. By passing that to localtime() you are saying "what is the epoch number 1,200 when converted to a date and time in my local timezone?" 1,200 is 20 past midnight on Jan 1st 1970 GMT. But in your local, Frankfurt, timezone, it's 20 past 1am. Which is why you're getting 1:20 and I'm getting 0:20.
There are a couple of ways to fix this. You can do the conversion manually.
my $duration = 1_200;
my $mins = int($duration/60);
my $secs = $duration % 60;
Or you can use a proper date/time handling module like DateTime (along with its associated module DateTime::Duration).
It might work if you use timegm() and gmtime() in place of timelocal() and localtime() - but I really don't recommend this approach as it perpetuates the confusion between points in time and durations.
Update: A version using DateTime.
#/usr/bin/perl
use strict;
use warnings;
use DateTime;
use DateTime::Format::Strptime;
my $h_end = '2018.11.12 00:50:00';
my $h_start = '2018.11.12 00:30:00';
my $date_p = DateTime::Format::Strptime->new(
pattern => '%Y.%m.%d %H:%M:%S'
);
my $duration = $date_p->parse_datetime($h_end)
- $date_p->parse_datetime($h_start);
printf '%02d:%02d:%02d', $duration->in_units('hours', 'minutes', 'seconds');
1200, the value of $duration, signifies the following when treated as a epoch timestamp
1970-01-01T01:20:00+01:00
^^^^^^^^
The solution is to replace
strftime("%H:%M:%S", localtime($duration));
with
strftime("%H:%M:%S", gmtime($duration));
This gives
1970-01-01T00:20:00Z
^^^^^^^^
Of course, this is still a hack. You're not suppose to be passing a duration to gmtime. Use an appropriate module instead.
use DateTime::Format::Strptime qw( );
my $format = DateTime::Format::Strptime->new(
pattern => '%Y.%m.%d %H:%M:%S',
on_error => 'croak',
);
my $h_end = $format->parse_datetime('2018.11.12 00:50:00');
my $h_start = $format->parse_datetime('2018.11.12 00:30:00');
my $dur = $h_end - $h_start;
printf "%02d:%02d:%02d\n", $dur->in_units(qw( hours minutes seconds ));
By the way,
timelocal($a[5],$a[4],$a[3],$a[2],$a[1],$a[0])
should be
timelocal($a[5],$a[4],$a[3],$a[2],$a[1]-1,$a[0])

Compare date time zone with time() in perl

I am trying to compare a file creation time which is in the format: 08-07-2016 08:16:26 GMT with the current time using time() in perl.
Since time() returns epoch time, I am not sure how to find the time difference between these two different time formats.
I tried something like below and for obvious reasons, I get an error saying: "Argument 08-07-2016 08:16:26 GMT" isn't numeric in subtraction".
my $current_time = time();
my $time_diff = $creation_time - $current_time;
if ($time_diff > 10) { #compare if the difference is greater than 10hours
# do something...
}
Some of the questions I have:
Since I want to compare only the hour difference, how can I extract just the hours from both these time formats?
I am unsure if the comparison of $time_diff > 10 is right. How to represent 10hours? 10*60?
OR is there a way to at least convert any given time format into epoch using DateTime or Time::Local?
How can I pass a a date parameter to a DateTime constructor?
my $dt1 = DateTime-> new (
year =>'1998',
month =>'4',
day =>'4',
hour =>'21',
time_zone =>'local'
);
Instead can we do something like
my $date = '08-07-2016 08:16:26 GMT';
my $dt1 = DateTime->new($date); # how can i pass a parameter to the constructor
print Dumper($dt1->epoch);
Thanks in advance for any help.
Time::Piece has been a standard part of Perl since 2007.
#!/usr/bin/perl
use strict;
use warnings;
use 5.010;
use Time::Piece;
use Time::Seconds;
my $creation_string = '08-07-2016 08:16:26 GMT';
my $creation_time = Time::Piece->strptime($creation_string, '%d-%m-%Y %H:%M:%S %Z');
my $current_time = gmtime;
my $diff = $current_time - $creation_time;
say $diff; # Difference in seconds
say $diff->pretty;

Adding MM::SS times, dividing them and printing the average

Let's say I just ran for 10 km and I now have 10 1km split times in the form of MM::SS. I want a simple way to add up an arbitrary number of the split times and average them. For instance, maybe I want to see how much faster (or slower) the last 5 km were when compared with the first 5 km.
I can do this myself by parsing the times, dividing them into seconds and then converting them back to MM::SS. The math isn't hard, but I was wondering if something on CPAN already does this in a simple way. My first attempt was using DateTime, but it doesn't convert from seconds to minutes, because of leap seconds.
To be clear, I don't care about leap seconds in this context, but I'm curious as to whether a library exists. As an example, here is what I have tried.
#!/usr/bin/env perl
use strict;
use warnings;
use feature qw( say );
use DateTime::Format::Duration;
my $formatter = DateTime::Format::Duration->new( pattern => '%M:%S' );
my #splits = #ARGV;
my $split_number = #splits;
my $total = $formatter->parse_duration( shift #splits );
foreach my $split (#splits) {
$total->add_duration(
$formatter->parse_duration($split) );
}
say 'Total time: ' . join ':', $total->minutes, $total->seconds;
$total->multiply( 1 / $split_number );
say 'Average time: ' . join ':', $total->minutes, $total->seconds;
say 'Using DateTime::Format::Duration: ' . $formatter->format_duration( $total );
And a sample run:
$ perl script/add-splits.pl 1:30 2:30
Total time: 3:60
Average time: 1.5:30
Using DateTime::Format::Duration: 01:30
So, you can see that the duration object itself, gives me a correct answer, but not something that a human wants to decipher.
DateTime::Format::Duration tries to be helpful, but tosses out 30 seconds in the process.
I'm not looking for the raw code to do this. I am interested in whether this already exists on CPAN.
The problem with finding this exact functionality on CPAN is that you want to manipulate strings that are time intervals. Most modules are concerned with the context of such strings, working with them as date and time. So it's hard to find something that simply adds mm:ss format. Since this quest is rather specific, and simple to write, why not wrap it in your own package?
Having said that, see whether the snippet below fits what you are looking for.
This is a simple solution with the core module Time::Piece. It does go to seconds to do the math, but it can be wrapped in a few subs that are then also easily extended for other calculations.
use warnings 'all';
use strict;
use feature 'say';
use Time::Piece;
use POSIX 'strftime';
use List::Util 'sum';
my #times = #ARGV;
my $fmt = '%M:%S';
my $tot_sec = sum map { Time::Piece->strptime($_, $fmt)->epoch } #times;
my $ave_sec = sprintf("%.0f", $tot_sec/#times); # round the average
my ($tot, $ave) = map { strftime $fmt, gmtime $_ } ($tot_sec, $ave_sec);
say "Total time: $tot";
say "Average time: $ave";
For manip_times.pl 2:30 1:30 this prints
Total time: 04:00
Average time: 02:00
We use strptime from Time::Piece to get the object, and then its epoch method returns seconds, which are added and averaged. This is converted back to mm:ss using strftime from POSIX. The Time::Piece also has strftime but to use it we'd have to have an object.
Note that Time::Piece does subtract its objects directly, $t1 - $t2, but it cannot add them. One can add an integer (seconds) to an object though, $t1 + 120. Also see the core Time::Seconds, a part of the distribution.
Comments on the method used in the question
The DateTime::Duration objects that are used cannot convert between different units
See the How DateTime Math Works section of the DateTime.pm documentation for more details. The short course: One cannot in general convert between seconds, minutes, days, and months, so this class will never do so.
From a bit further down the page, we see what conversions can be done and the related ones are only "hours <=> minutes" and "seconds <=> nanoseconds". The reasons have to do with leap seconds, DST, and such. Thus the calculation has to produce results such as 1.5 minutes.
The Class::Date also looks suitable for these particular requirements.
Here is a version using the DateTime object. Since it doesn't handle date parsing, we have to split the strings ourselves...
add_splits.pl
#!/usr/bin/env perl
use strict;
use warnings;
use feature qw( say );
use DateTime;
my #splits = #ARGV;
my $split_number = #splits;
my $fmt = "%M:%S";
my $total = DateTime->from_epoch( epoch => 0 );
foreach my $split (#splits) {
my ($split_min, $split_sec) = split /:/, $split;
$total->add( minutes => $split_min, seconds => $split_sec );
}
say 'Total time: ' . $total->strftime($fmt);
my $avg_seconds = $total->epoch / $split_number;
my $avg = DateTime->from_epoch( epoch => 0 )->add( seconds => $avg_seconds );
say 'Average time: ' . $avg->strftime($fmt);
Output
$ perl add_splits.pl 1:30 2:30
Total time: 04:00
Average time: 02:00

Fractional part of epoch

I have an epoch value of 1372252864.901871 which needs to be converted to localtime in perl. I know that localtime($val) where $val is set to 1372252864.901871 converts the value to local time. But I also need to add values of 100 msecs or say 86.7 msecs to this and determine the computed local time. How do I do this? Does the function localtime() take care of fractional epoch value that I have given?
If you just want to add to an epoch time and get another epoch time, just do:
1372252864.901871 + .0867
If you are doing something else, no, localtime does not preserve fractional seconds. You probably want to use DateTime to deal with fractional seconds:
use DateTime;
my $time = DateTime->from_epoch(epoch=>1372252864.901871, time_zone=>'local');
$time->add(nanoseconds=>86.7e6);
print $time->epoch + $time->nanosecond / 1e9;
(time_zone isn't needed for this example, but sounds like what you might want for other things you might be doing with this time)
You might want Time::HiRes:
The Time::HiRes module implements a Perl interface to the usleep,
nanosleep, ualarm, gettimeofday, and setitimer/getitimer system calls,
in other words, high resolution time and timers
The localtime function breaks down a raw time like 1372252864 into year, month, day, etc. Any fractional part of the raw time is quietly ignored.
I'm not aware of any function, either built-in or in a module, that supports broken-down times with a resolution finer than 1 second.
But since you already have the fractional seconds, I'd say you can already do what you need to do. Just use localtime (or gmtime) to break down the integer part of your raw timestamp, and then take the fractional part:
$timestamp = 1372252864.901871;
#parts = localtime($timestamp);
$fraction = $timestamp - int($timestamp);
$milliseconds = int($fraction * 1000.0);
It should be easy enough to write your own high-resolution replacement for localtime that does this for you.
If you need to add 86.7 milliseconds to a timestamp, you'll need to use the raw form anyway:
$timestamp = 1372252864.901871;
$timestamp += 86.7/1000.0;
The simplest way that occurs to me is to use Time::HiRes::Value to handle arithmetic on times to 1μs accuracy, and Time::Piece to format the result as a date/time string.
Time::HiRes::Value holds the time as a two-element array; the first field has the whole seconds while the second has the number of microseconds. That's the same format that Time::HiRes functions work with, but there's nothing that I know of that will natively convert to a date/time string so I've used Time::Piece to convert the whole seconds and just appended the fractional seconds.
It looks like this
use strict;
use warnings;
use Time::HiRes::Value;
use Time::Piece;
my $dt = Time::HiRes::Value->new(1372252864.901871);
my $delta = Time::HiRes::Value->new(86.7E-3); # 86.7ms
$dt += $delta;
print dt_to_string($dt), "\n";
sub dt_to_string {
my ($dt) = #_;
Time::Piece->new($dt->[0])->datetime =~ s{(\d+)\z}{$1 + $dt->[1] / 1E6}er;
}
output
2013-06-26T14:21:4.98857