Cadz
Cadz

Reputation: 151

How to round up a timestamp to nearest 5 minute value

I have this format for a timestamp:

2019-01-24T00:00:05.011719

How do I round this up to the nearest 5 minute value? in perl

Edit: Okay the question is pretty vague and lazy. But the question stays, just want to add information to my problem.

  1. I don't know what kind of format that is, so if I know i can google, what function or method converts those kind of date formats.

  2. 20190124000000 isn't this the correct 5 min value? I wanted it to be like that format by the way. But its too easy to be the correct approach, it makes me suspicious like answering math questions back in college. Anyway, I can just use regex for that.

Upvotes: 0

Views: 1494

Answers (2)

Stefan Becker
Stefan Becker

Reputation: 5962

The problem can be broken down to:

  • pick a time period N time units
  • pick a starting point (epoch) where the first (0th) time period starts
  • parse the timestamp and convert it into "X time units since epoch"
    • integer division of X by N gives you the # of the period the timestamp lies in
    • the remainder of the division of X by N gives you the offset inside that period
    • if the remainder is exactly 0 (zero) then the timestamp is at the start of that period

Your requirements are

  • N is 5 minutes, or 300 seconds
  • let's use the standard UNIX Epoch, i.e. 1970-01-01T00:00:00Z
  • you want to round up to the next period, unless the timestamp is exactly the start of a period.
  • as you have not given a time zone, I assume UTC for simplicity.

Just using core Perl, i.e. Time::Piece, a solution would be:

#!/usr/bin/perl
use warnings;
use strict;

use constant PERIOD => 5 * 60; # 5 minutes

use Time::Piece;

# timezone for Time::Piece->new()
$ENV{TZ} = "UTC";

while (<DATA>) {
    chomp;
    my($iso8601, $fractional) = split(/\./);

    # NOTE: time is interpreted as UTC
    my $t = Time::Piece->strptime($iso8601, '%FT%T');

    # calculate multiple of PERIOD and offset in that period
    my $index  = int($t->epoch / PERIOD);
    my $offset = $t->epoch % PERIOD + "0.${fractional}";

    # round up to next PERIOD unless time is exactly multiple of PERIOD
    $index++ if $offset > 0;

    # convert index back to epoch and generate new Time::Piece object
    # NOTE: timezone offset is set to $ENV{TZ} timezone
    my $t2 = Time::Piece->new($index * PERIOD, 0);

    print "$_ -> ", $t2->strftime('%FT%T'), "\n";
}

exit 0;

__DATA__
2019-01-24T00:00:00.000000
2019-01-24T00:00:05.011719
2019-01-24T00:04:59.999999
2019-01-24T00:05:00.000000
2019-07-24T00:00:00.000000
2019-07-24T00:00:05.011719
2019-07-24T00:04:59.999999
2019-07-24T00:05:00.000000

Test run:

$ perl dummy.pl
2019-01-24T00:00:00.000000 -> 2019-01-24T00:00:00
2019-01-24T00:00:05.011719 -> 2019-01-24T00:05:00
2019-01-24T00:04:59.999999 -> 2019-01-24T00:05:00
2019-01-24T00:05:00.000000 -> 2019-01-24T00:05:00
2019-07-24T00:00:00.000000 -> 2019-07-24T00:00:00
2019-07-24T00:00:05.011719 -> 2019-07-24T00:05:00
2019-07-24T00:04:59.999999 -> 2019-07-24T00:05:00
2019-07-24T00:05:00.000000 -> 2019-07-24T00:05:00

Upvotes: 1

ikegami
ikegami

Reputation: 385916

use DateTime::Format::Strptime qw( );

my $format = DateTime::Format::Strptime->new(
    pattern   => '%FT%T.%6N',
    time_zone => 'UTC',       # Or 'local' or 'America/Toronto' or '-0500'
    on_error  => 'croak',
);

my $dt = $format->parse_datetime('2019-01-24T00:00:05.011719');
$dt->set_formatter($format);  # Set default stringification format.

$dt->truncate( to => 'second' )->add( seconds => 1 ) if $dt->nanosecond;
$dt->truncate( to => 'minute' )->add( minutes => 1 ) if $dt->second;
$dt->add( minutes => 5 - ( $dt->minute % 5 ) ) if $dt->minute % 5;

say $dt;  # 2019-01-24T00:05:00.000000

Note that the format used by 2019-01-24T00:00:05.011719 is ambiguous if the associated time zone observes DST (because of the repeated hour in the fall).

Aside from that, the above code correctly handles discontinuities in the passing of time such as the ones that occur at DST changes, as long as the discontinuity starts and ends at a rounding point.

Upvotes: 3

Related Questions