http://qs321.pair.com?node_id=1202481

Cody Fendant has asked for the wisdom of the Perl Monks concerning the following question:

My current project involves me figuring out how long an email took to be delivered.

I send the email with perl, so I have the date handy, no problems there.

I check Gmail to see when it was received, and I can get the Received: header from Gmail to see when it was received, now all I need to do is compare those two.

This is where it gets complicated. I'm in Australia, and Google's date header on the email is like this:

Tue, 31 Oct 2017 15:07:32 -0700 (PDT)

So, I send mail at 09:07:05 Sydney time (Wednesday November 1) and it's received at 15:07:32 (Tuesday October 31) PDT a.k.a. thirty seconds later, but in Californian time. How do I normalise those two times?

I put my $sending_datetime into a DateTime object, and I parse the Gmail date, using DateTime::Format::Mail, into another DateTimeObject (I have to strip the "(PDT)" part or it won't parse). I specifically set the timezone of the two dates to Australia/Sydney and America/Los_Angeles.

Then I get the UTC offset of the two dates, to compare, using DateTime's $dt->offset() method which should give me "the offset from UTC, in seconds, of the datetime object according to the time zone."

Those two values should be a few seconds apart, but I get 39600 for the time sent and -25200 for the time received. What am I doing wrong?

Replies are listed 'Best First'.
Re: Comparing DateTime objects in different timezones
by hippo (Bishop) on Oct 31, 2017 at 23:05 UTC

    It's trivial using Time::Piece:

    #!/usr/bin/env perl use strict; use warnings; use Time::Piece; my $fmt = '%a, %e, %b %Y %T %z'; my $sent = Time::Piece->strptime ('Wed, 01 Nov 2017 09:07:05 +1100', $ +fmt); my $recd = Time::Piece->strptime ('Tue, 31 Oct 2017 15:07:32 -0700', $ +fmt); my $duration = $recd - $sent; print "Took $duration seconds\n";
Re: Comparing DateTime objects in different timezones
by tangent (Parson) on Oct 31, 2017 at 23:39 UTC
    $dt->offset is giving you the +1100 and -0700 portion of each time format, but in seconds rather than hours, i.e. 11 hours = 39600 seconds and -7 hours = -25200 seconds. You could use the $dt->epoch method to get the duration in seconds:
    my $duration = $dt_received->epoch - $dt_sent->epoch; print "$duration seconds"; # 27 seconds
    For finer grained control of the result you can use subtract_datetime()
    my $duration = $dt_received->subtract_datetime( $dt_sent ); print $duration->minutes, " minutes, ", $duration->seconds, " seconds" +; # 0 minutes, 27 seconds
    See DateTime::Duration
      Oh that makes sense. Thank you so much, I just misinterpreted the "offset" as being the offset of the time from UTC, and not the offset of the time zone from UTC. I should have been suspicious that they were such nice round numbers!
Re: Comparing DateTime objects in different timezones
by haukex (Archbishop) on Nov 01, 2017 at 10:50 UTC
    I specifically set the timezone of the two dates to Australia/Sydney and America/Los_Angeles.

    Be careful with this, since using set_time_zone may actually change the time value. If you have parsed times that include time zone information, then you do not need to use set_time_zone unless you specifically want to represent the time in another zone, but this is not necessary for comparisons! Only if your time did not include time zone information and it is in the special "floating" zone should you use set_time_zone, because as per the DateTime docs, comparisons are best between objects in a specific time zone (even if it's just UTC). See also my post here.

    Then I get the UTC offset of the two dates, to compare

    It sounds like you might be over-thinking the solution - DateTime allows for comparison of objects even if they are in different time zones. See its docs: subtract_datetime_absolute "is the only way to accurately measure the absolute amount of time between two datetimes, since units larger than a second do not represent a fixed number of seconds." For example (from here):

    my $diff_sec = $dt2->subtract_datetime_absolute($dt1)     ->in_units('seconds');

    So your steps should be:

    1. Parse your strings into DateTime objects (e.g. DateTime::Format::Strptime)
    2. For debugging, print out your objects including TZ info (e.g.  $dt->strftime('%Y-%m-%d %H:%M:%S.%3N %Z (%z)'))
    3. If and only if any of the objects are in the "floating" zone, set their time zone.
    4. Use subtract_datetime_absolute to compare them, as shown above.

    Since the DateTime family are my favorite modules for date/time processing, I've written about them a lot, you should be able to find some good snippets among my posts. Update: In fact, this post should give you everything you need!

Re: Comparing DateTime objects in different timezones
by thanos1983 (Parson) on Nov 01, 2017 at 08:18 UTC

    Hello Cody Fendant,

    Fellow Monks have provided you with the answer to your question. I would like to add another option here with one of my favorite modules regarding time manipulations Date::Manip.

    Small sample of code with different timezone(s) and time subtraction. You can apply the time subtraction based on your time of receiving the email etc...

    #!/usr/bin/perl use strict; use warnings; use Date::Manip; use feature 'say'; my $tz = new Date::Manip::TZ; my $dateLocal = ParseDate('now'); say $dateLocal; # From timeZone To timeZone my $dateTimeZone = Date_ConvTZ($dateLocal,"GMT","CST"); my $unixLocal = UnixDate($dateLocal,'%Y-%m-%d-%H-%M-%S'); say 'Local Timezone: ' . $unixLocal; my $unixTimeZone = UnixDate($dateTimeZone,'%Y-%m-%d-%H-%M-%S'); say 'Different Timezone: ' . $unixTimeZone; my $date1 = ParseDate($dateLocal); my $date2 = ParseDate($dateTimeZone); my $delta = DateCalc($date1, $date2, 1); say 'Difference between Timezones: ' . $delta; # Change Format my $str = UnixDate($dateLocal,"It is now %T on %b %e, %Y."); say $str; my $str_second = UnixDate($dateLocal,"It is now %T on %B %e, %Y."); say $str_second; my $str_decimal = UnixDate($dateLocal,"It is now decimal %T on %B %d, +%Y."); say $str_decimal; # Add more time or subtract or do what ever you want with time my $deltastr = "12 hours ago"; my $date_past = DateCalc($dateLocal,$deltastr); # say $date; my $str_past = UnixDate($date_past,"It was 12 hours ago %T on %B %d, % +Y."); say $str_past; __END__ $ perl test.pl 2017110109:11:31 Local Timezone: 2017-11-01-09-11-31 Different Timezone: 2017-11-01-17-11-31 Difference between Timezones: 0:0:0:0:8:0:0 It is now 09:11:31 on Nov 1, 2017. It is now 09:11:31 on November 1, 2017. It is now decimal 09:11:31 on November 01, 2017. It was 12 hours ago 21:11:31 on October 31, 2017.

    Hope this helps, BR.

    Seeking for Perl wisdom...on the process of learning...not there...yet!