Perl strptime parse millisecond - perl

I need to parse some time string that comes in a format like ddmmyyyyhhmmssXXX. The XXX part is millisecond. In the below code Im ignoring the millisecond part. It works but I get the error:
garbage at end of string in strptime: 293 at /usr/local/lib64/perl5/Time/Piece.pm line 482.
Whats the proper format that I should use.
$time = '11032014182819802';
$format = '%d%m%Y%H%M%S';
$t = Time::Piece->strptime($time, $format);

Time::Piece->strptime(substr($time, 0, -3), $format);
since Time::Piece does not support milliseconds.

If you care about the milliseconds and want to preserve them, you'll need to look into using something else (e.g., DateTime::Format::Strptime).

You can use DateTime::Format::Strptime if you want to parse milliseconds, then you may try this:
my $Strp = new DateTime::Format::Strptime(
pattern => '%d%m%Y%H%M%S%3N',
);
my $date = $Strp->parse_datetime("11032014182819802");
print $date->millisecond ,"\n";

Related

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])

Converting ISO8601 duration (P3Y6M...) to seconds in perl

I'm trying to convert an ISO 8601 time duration format, P3Y6M4DT12H30M5S, to seconds in perl. Is there a library that does this? I've tried searching, but I've only found a js library. Looking through DateTime and Time::Moment didn't provide a solution either.
There is DateTime::Format::Duration::ISO8601, which can convert the ISO 8601 duration string into a DateTime::Duration object. You can then convert that to seconds.
use DateTime::Duration;
use DateTime::Format::Duration::ISO8601;
my $format = DateTime::Format::Duration::ISO8601->new;
my $d = $format->parse_duration('P3Y6M4DT12H30M5S');
However, the DateTime::Duration format cannot be used to convert a year to seconds, as explained in the docs here.
The last example demonstrates that there will not be any conversion
between units which don't have a fixed conversion rate. The only
conversions possible are:
years <=> months
weeks <=> days
hours <=> minutes
seconds <=> nanoseconds
For the explanation of why this is the case, please see the How
DateTime Math Works section of the DateTime.pm documentation
You can use DateTime::Format::Duration with the %s pattern to circumvent that. A complete implementation might look like this.
use DateTime::Duration;
use DateTime::Format::Duration::ISO8601;
use DateTime::Format::Duration;
my $format = DateTime::Format::Duration::ISO8601->new;
my $d = $format->parse_duration('P3Y6M4DT12H30M5S');
my $output_format = DateTime::Format::Duration->new( pattern => '%s' );
print $output_format->format_duration($d);
Or, in short if you only need it once.
use DateTime::Duration;
use DateTime::Format::Duration::ISO8601;
use DateTime::Format::Duration;
print DateTime::Format::Duration->new( pattern => '%s' )
->format_duration(
DateTime::Format::Duration::ISO8601->new->parse_duration('P3Y6M4DT12H30M5S') );
Both of these will print
109254605

How to convert unknown date/time format into normal using Perl? [duplicate]

This question already has answers here:
How can I parse dates and convert time zones in Perl?
(5 answers)
Closed 7 years ago.
I have a date/time like this: 2015-07-31T13:30:00.000+01:00
And I want to convert it to normal date and time using Perl and Time::Piece->strptime
Here is my code:
sub changeDateFormat {
my ($date, $fromFormat, $toFormat) = (#_);
return Time::Piece->strptime($date, $fromFormat)->strftime($toFormat);
}
The call:
print changeDateFormat($that_date, '%Y-%m-%dT%H:%M:%S.%N+%z', '%Y:%m:%d');
I think that .000 are nano seconds and +01.00 stands for time zone.
But the given code gives this:
Error parsing time at /usr/lib64/perl5/Time/Piece.pm line 470
Any help is appreciated.
There's a couple of problems I think.
%N isn't in my strftime manpage. So that might well not work.
And %z - I'm pretty sure +01:00 isn't valid.
%z The +hhmm or -hhmm numeric timezone (that is, the hour and
minute offset from UTC). (SU)
This works though:
my $date = '2015-07-31T13:30:00+0100';
my $fromFormat = '%Y-%m-%dT%H:%M:%S%z';
print Time::Piece->strptime($date, $fromFormat);
So I'd suggest - unless your milliseconds are important - you could just strip those via a regex, and likewise the timezone. (And it they are important, I don't think Time::Piece does ms resolution anyway)
You can probably use a regular expression to 'correct' your input date if you were so inclined. I'm unsure if fits your use case but:
$date =~ s/\+(\d{2}):(\d{2})$/+$1$2/;
$date =~ s/\.\d{3}+/+/;
You can use strptime in Time::Piece and adding the time zone manually as shown in this answer, or you could try using DateTime::Format::Strptime instead:
use feature qw(say);
use strict;
use warnings;
use DateTime::Format::Strptime;
my $timestamp = '2015-07-31T13:30:00.000+0100';
my $strp = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%dT%H:%M:%S.%N%z'
);
my $dt = $strp->parse_datetime( $timestamp );
say $dt->strftime('%Y:%m:%d');
Output:
2015:07:31
use DateTime;
use DateTime::Format::ISO8601;
use DateTime::Format::Strptime;
my $string = '2015-07-31T13:30:00.000+01:00';
my $date = DateTime::Format::ISO8601->parse_datetime( $string );
die "Error" unless $date;
my $formatter = new DateTime::Format::Strptime(pattern => '%Y-%m-%d %T');
$date->set_formatter($formatter);
print "$date\n";

Parse date String to milliseconds and vice versa

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.

How do I convert datetime to a different format? [perl]

Need help parsing the datetime stamp and splitting it up by date and time.
use strict;
use warnings;
use Time::Piece;
my $string = "05:57:03 08/31/10 MDT";
print $string,"\n";
my $time = Time::Piece->strptime($string, "%H:%M:%S");
my $date = Time::Piece->strptime($string, "%Y/%m/%d");
print $time,$date,"\n";
Thanks! Also how do I figure out which day of week this is using code?
use DateTime::Format::Strptime;
my $s = DateTime::Format::Strptime->new(pattern => '%T %D %Z');
my $dt = $s->parse_datetime('05:57:03 08/31/10 MDT');
say $dt->strftime('%A'); # Tuesday
You should be able to use code like the following:
my $t = Time::Piece->strptime($string, "%H:%M:%S %m/%d/%y %Z");
However, on my system at least, I have to change the time zone MST to GMT for it to match; if I leave it as in your example, I get an error:
Perl> my $t = Time::Piece->strptime("05:57:03 08/31/10 DST", "%H:%M:%S %m/%d/%y %Z");
[!] Runtime error: Error parsing time at /usr/local/lib/perl/5.10.0/Time/Piece.pm line 469.
If it works for you, though, you'll have a Time::Piece object, on which you can call e.g. $t->day_of_week for the day of the week as a number, $t->day for e.g. 'Tue', or $t->fullday for e.g. 'Tuesday'.
See the documentation for Time::Piece for details on the methods you can call.