Skip to content

Instantly share code, notes, and snippets.

@chansen
Created February 20, 2016 19:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chansen/6dde6200729f1fdde7fb to your computer and use it in GitHub Desktop.
Save chansen/6dde6200729f1fdde7fb to your computer and use it in GitHub Desktop.
epoch_to_datetime.pl
#!/usr/bin/perl
use strict;
use warnings;
use Time::Local qw[];
# Computes the difference between local time and UTC/GMT time in minutes
sub _local_time_offset {
my ($time) = @_;
return int((Time::Local::timegm(localtime($time)) - $time) / 60);
}
# Formats the given epoch to a ISO 8601 calendar date with time of day and
# a zone designator in extended format.
#
# YYYY-MM-DDThh:mm:ssZ
# YYYY-MM-DDThh:mm:ss.sssZ
# YYYY-MM-DDThh:mm:ss.ssssssZ
# YYYY-MM-DDThh:mm:ss±hh:mm (only if $local = true)
# YYYY-MM-DDThh:mm:ss.sss±hh:mm (only if $local = true)
# YYYY-MM-DDThh:mm:ss.ssssss±hh:mm (only if $local = true)
#
sub epoch_to_datetime {
my ($epoch, $local) = @_;
$epoch //= time;
my $time = int $epoch;
my $fraction = $epoch - $time;
if (!$fraction) {
$fraction = '';
}
else {
if ($fraction < 0) {
$fraction += 1;
$time -= 1;
}
# Round to nearest microsecond
my $usec = int($fraction * 1E6 + 0.5);
# Format fractional seconds as .fff (millis) or .ffffff (micros)
if (($usec % 1000) == 0) {
$fraction = sprintf '.%.3d', $usec / 1000;
}
else {
$fraction = sprintf '.%.6d', $usec;
}
}
my $zone = 'Z';
if ($local) {
# Difference between local time and UTC/GMT time in minutes
my $offset = _local_time_offset($time);
if ($offset) {
# Format the difference as ±hh:mm
$zone = sprintf('%+.2d:%.2d', $offset / 60, abs($offset) % 60);
# Adjust time to account for the local time
$time += $offset * 60;
}
}
my @tm = (gmtime($time))[0..5];
$tm[5] += 1900;
$tm[4] += 1;
return sprintf '%.4d-%.2d-%.2dT%.2d:%.2d:%.2d%s%s',
reverse(@tm), $fraction, $zone;
}
use Test::More 0.88;
{
my @tests = (
[ '1920-12-31T23:59:59Z', -1546300801.0 ],
[ '1920-12-31T23:59:59.000001Z', -1546300800.999999046325684 ],
[ '1920-12-31T23:59:59.000002Z', -1546300800.999998092651367 ],
[ '1920-12-31T23:59:59.000010Z', -1546300800.999989986419678 ],
[ '1920-12-31T23:59:59.000020Z', -1546300800.999979972839355 ],
[ '1920-12-31T23:59:59.000100Z', -1546300800.999900102615356 ],
[ '1920-12-31T23:59:59.000200Z', -1546300800.999799966812134 ],
[ '1920-12-31T23:59:59.001Z', -1546300800.999000072479248 ],
[ '1920-12-31T23:59:59.002Z', -1546300800.997999906539917 ],
[ '1920-12-31T23:59:59.010Z', -1546300800.990000009536743 ],
[ '1920-12-31T23:59:59.020Z', -1546300800.980000019073486 ],
[ '1920-12-31T23:59:59.100Z', -1546300800.900000095367432 ],
[ '1920-12-31T23:59:59.192Z', -1546300800.808000087738037 ],
[ '1920-12-31T23:59:59.192080Z', -1546300800.807919979095459 ],
[ '1920-12-31T23:59:59.192090Z', -1546300800.807909965515137 ],
[ '1920-12-31T23:59:59.192098Z', -1546300800.807902097702026 ],
[ '1920-12-31T23:59:59.192099Z', -1546300800.807900905609131 ],
[ '1920-12-31T23:59:59.200Z', -1546300800.799999952316284 ],
[ '1920-12-31T23:59:59.300Z', -1546300800.700000047683716 ],
[ '1920-12-31T23:59:59.400Z', -1546300800.599999904632568 ],
[ '1920-12-31T23:59:59.419200Z', -1546300800.58080005645752 ],
[ '1920-12-31T23:59:59.419209Z', -1546300800.580790996551514 ],
[ '1920-12-31T23:59:59.490Z', -1546300800.509999990463257 ],
[ '1920-12-31T23:59:59.499Z', -1546300800.500999927520752 ],
[ '1920-12-31T23:59:59.499900Z', -1546300800.500099897384644 ],
[ '1920-12-31T23:59:59.500Z', -1546300800.5 ],
[ '1920-12-31T23:59:59.500001Z', -1546300800.499999046325684 ],
[ '1920-12-31T23:59:59.500010Z', -1546300800.499989986419678 ],
[ '1920-12-31T23:59:59.500100Z', -1546300800.499900102615356 ],
[ '1920-12-31T23:59:59.501Z', -1546300800.499000072479248 ],
[ '1920-12-31T23:59:59.510Z', -1546300800.490000009536743 ],
[ '1920-12-31T23:59:59.600Z', -1546300800.400000095367432 ],
[ '1920-12-31T23:59:59.700Z', -1546300800.299999952316284 ],
[ '1920-12-31T23:59:59.800Z', -1546300800.200000047683716 ],
[ '1920-12-31T23:59:59.900Z', -1546300800.099999904632568 ],
[ '1920-12-31T23:59:59.980Z', -1546300800.019999980926514 ],
[ '1920-12-31T23:59:59.990Z', -1546300800.009999990463257 ],
[ '1920-12-31T23:59:59.998Z', -1546300800.002000093460083 ],
[ '1920-12-31T23:59:59.999Z', -1546300800.000999927520752 ],
[ '1920-12-31T23:59:59.999800Z', -1546300800.000200033187866 ],
[ '1969-12-31T23:59:58Z', -2.0 ],
[ '1969-12-31T23:59:58.500Z', -1.5 ],
[ '1969-12-31T23:59:58.800Z', -1.2 ],
[ '1969-12-31T23:59:58.900Z', -1.1 ],
[ '1969-12-31T23:59:58.980Z', -1.02 ],
[ '1969-12-31T23:59:58.990Z', -1.01 ],
[ '1969-12-31T23:59:58.998Z', -1.002 ],
[ '1969-12-31T23:59:58.999Z', -1.001 ],
[ '1969-12-31T23:59:58.999800Z', -1.0002 ],
[ '1969-12-31T23:59:58.999900Z', -1.0001 ],
[ '1969-12-31T23:59:58.999980Z', -1.00002 ],
[ '1969-12-31T23:59:58.999990Z', -1.00001 ],
[ '1969-12-31T23:59:58.999998Z', -1.000002 ],
[ '1969-12-31T23:59:58.999999Z', -1.000001 ],
[ '1969-12-31T23:59:59Z', -1.0 ],
[ '1969-12-31T23:59:59.000001Z', -0.999999 ],
[ '1969-12-31T23:59:59.000002Z', -0.999998 ],
[ '1969-12-31T23:59:59.000010Z', -0.99999 ],
[ '1969-12-31T23:59:59.000020Z', -0.99998 ],
[ '1969-12-31T23:59:59.000100Z', -0.9999 ],
[ '1969-12-31T23:59:59.000200Z', -0.9998 ],
[ '1969-12-31T23:59:59.001Z', -0.999 ],
[ '1969-12-31T23:59:59.002Z', -0.998 ],
[ '1969-12-31T23:59:59.010Z', -0.99 ],
[ '1969-12-31T23:59:59.020Z', -0.98 ],
[ '1969-12-31T23:59:59.100Z', -0.9 ],
[ '1969-12-31T23:59:59.200Z', -0.8 ],
[ '1969-12-31T23:59:59.300Z', -0.7 ],
[ '1969-12-31T23:59:59.400Z', -0.6 ],
[ '1969-12-31T23:59:59.490Z', -0.51 ],
[ '1969-12-31T23:59:59.499Z', -0.501 ],
[ '1969-12-31T23:59:59.499900Z', -0.5001 ],
[ '1969-12-31T23:59:59.499990Z', -0.50001 ],
[ '1969-12-31T23:59:59.499999Z', -0.500001 ],
[ '1969-12-31T23:59:59.500Z', -0.5 ],
[ '1969-12-31T23:59:59.500001Z', -0.499999 ],
[ '1969-12-31T23:59:59.500010Z', -0.49999 ],
[ '1969-12-31T23:59:59.500100Z', -0.4999 ],
[ '1969-12-31T23:59:59.501Z', -0.499 ],
[ '1969-12-31T23:59:59.510Z', -0.49 ],
[ '1969-12-31T23:59:59.600Z', -0.4 ],
[ '1969-12-31T23:59:59.700Z', -0.3 ],
[ '1969-12-31T23:59:59.800Z', -0.2 ],
[ '1969-12-31T23:59:59.900Z', -0.1 ],
[ '1969-12-31T23:59:59.980Z', -0.02 ],
[ '1969-12-31T23:59:59.990Z', -0.01 ],
[ '1969-12-31T23:59:59.998Z', -0.002 ],
[ '1969-12-31T23:59:59.999Z', -0.001 ],
[ '1969-12-31T23:59:59.999800Z', -0.0002 ],
[ '1969-12-31T23:59:59.999900Z', -0.0001 ],
[ '1969-12-31T23:59:59.999980Z', -0.00002 ],
[ '1969-12-31T23:59:59.999990Z', -0.00001 ],
[ '1969-12-31T23:59:59.999998Z', -0.000002 ],
[ '1969-12-31T23:59:59.999999Z', -0.000001 ],
[ '1970-01-01T00:00:00Z', 0.0 ],
[ '1970-01-01T00:00:00.000001Z', 0.000001 ],
[ '1970-01-01T00:00:00.000002Z', 0.000002 ],
[ '1970-01-01T00:00:00.000010Z', 0.00001 ],
[ '1970-01-01T00:00:00.000020Z', 0.00002 ],
[ '1970-01-01T00:00:00.000100Z', 0.0001 ],
[ '1970-01-01T00:00:00.000200Z', 0.0002 ],
[ '1970-01-01T00:00:00.001Z', 0.001 ],
[ '1970-01-01T00:00:00.002Z', 0.002 ],
[ '1970-01-01T00:00:00.010Z', 0.01 ],
[ '1970-01-01T00:00:00.020Z', 0.02 ],
[ '1970-01-01T00:00:00.100Z', 0.1 ],
[ '1970-01-01T00:00:00.200Z', 0.2 ],
[ '1970-01-01T00:00:00.300Z', 0.3 ],
[ '1970-01-01T00:00:00.400Z', 0.4 ],
[ '1970-01-01T00:00:00.490Z', 0.49 ],
[ '1970-01-01T00:00:00.499Z', 0.499 ],
[ '1970-01-01T00:00:00.499900Z', 0.4999 ],
[ '1970-01-01T00:00:00.499990Z', 0.49999 ],
[ '1970-01-01T00:00:00.499999Z', 0.499999 ],
[ '1970-01-01T00:00:00.500Z', 0.5 ],
[ '1970-01-01T00:00:00.500001Z', 0.500001 ],
[ '1970-01-01T00:00:00.500010Z', 0.50001 ],
[ '1970-01-01T00:00:00.500100Z', 0.5001 ],
[ '1970-01-01T00:00:00.501Z', 0.501 ],
[ '1970-01-01T00:00:00.510Z', 0.51 ],
[ '1970-01-01T00:00:00.600Z', 0.6 ],
[ '1970-01-01T00:00:00.700Z', 0.7 ],
[ '1970-01-01T00:00:00.800Z', 0.8 ],
[ '1970-01-01T00:00:00.900Z', 0.9 ],
[ '1970-01-01T00:00:00.980Z', 0.98 ],
[ '1970-01-01T00:00:00.990Z', 0.99 ],
[ '1970-01-01T00:00:00.999800Z', 0.9998 ],
[ '1970-01-01T00:00:00.999900Z', 0.9999 ],
[ '1970-01-01T00:00:00.999980Z', 0.99998 ],
[ '1970-01-01T00:00:00.999990Z', 0.99999 ],
[ '1970-01-01T00:00:00.999998Z', 0.999998 ],
[ '1970-01-01T00:00:00.999999Z', 0.999999 ],
[ '1970-01-01T00:00:01Z', 1.0 ],
[ '1970-01-01T00:00:01.000001Z', 1.000001 ],
[ '1970-01-01T00:00:01.000002Z', 1.000002 ],
[ '1970-01-01T00:00:01.000010Z', 1.00001 ],
[ '1970-01-01T00:00:01.000020Z', 1.00002 ],
[ '1970-01-01T00:00:01.000100Z', 1.0001 ],
[ '1970-01-01T00:00:01.000200Z', 1.0002 ],
[ '1970-01-01T00:00:01.001Z', 1.001 ],
[ '1970-01-01T00:00:01.002Z', 1.002 ],
[ '1970-01-01T00:00:01.010Z', 1.01 ],
[ '1970-01-01T00:00:01.020Z', 1.02 ],
[ '1970-01-01T00:00:01.100Z', 1.1 ],
[ '1970-01-01T00:00:01.200Z', 1.2 ],
[ '2020-12-31T23:59:59Z', 1609459199.0 ],
[ '2020-12-31T23:59:59.000001Z', 1609459199.000000953674316 ],
[ '2020-12-31T23:59:59.000002Z', 1609459199.000001907348633 ],
[ '2020-12-31T23:59:59.000010Z', 1609459199.000010013580322 ],
[ '2020-12-31T23:59:59.000020Z', 1609459199.000020027160645 ],
[ '2020-12-31T23:59:59.000100Z', 1609459199.000099897384644 ],
[ '2020-12-31T23:59:59.000200Z', 1609459199.000200033187866 ],
[ '2020-12-31T23:59:59.001Z', 1609459199.000999927520752 ],
[ '2020-12-31T23:59:59.002Z', 1609459199.002000093460083 ],
[ '2020-12-31T23:59:59.010Z', 1609459199.009999990463257 ],
[ '2020-12-31T23:59:59.020Z', 1609459199.019999980926514 ],
[ '2020-12-31T23:59:59.100Z', 1609459199.099999904632568 ],
[ '2020-12-31T23:59:59.200Z', 1609459199.200000047683716 ],
[ '2020-12-31T23:59:59.202Z', 1609459199.20199990272522 ],
[ '2020-12-31T23:59:59.202080Z', 1609459199.202080011367798 ],
[ '2020-12-31T23:59:59.202090Z', 1609459199.20209002494812 ],
[ '2020-12-31T23:59:59.202098Z', 1609459199.20209789276123 ],
[ '2020-12-31T23:59:59.202099Z', 1609459199.202099084854126 ],
[ '2020-12-31T23:59:59.300Z', 1609459199.299999952316284 ],
[ '2020-12-31T23:59:59.400Z', 1609459199.400000095367432 ],
[ '2020-12-31T23:59:59.420200Z', 1609459199.420200109481812 ],
[ '2020-12-31T23:59:59.420209Z', 1609459199.420208930969238 ],
[ '2020-12-31T23:59:59.490Z', 1609459199.490000009536743 ],
[ '2020-12-31T23:59:59.499Z', 1609459199.499000072479248 ],
[ '2020-12-31T23:59:59.499900Z', 1609459199.499900102615356 ],
[ '2020-12-31T23:59:59.500Z', 1609459199.5 ],
[ '2020-12-31T23:59:59.500001Z', 1609459199.500000953674316 ],
[ '2020-12-31T23:59:59.500010Z', 1609459199.500010013580322 ],
[ '2020-12-31T23:59:59.500100Z', 1609459199.500099897384644 ],
[ '2020-12-31T23:59:59.501Z', 1609459199.500999927520752 ],
[ '2020-12-31T23:59:59.510Z', 1609459199.509999990463257 ],
[ '2020-12-31T23:59:59.600Z', 1609459199.599999904632568 ],
[ '2020-12-31T23:59:59.700Z', 1609459199.700000047683716 ],
[ '2020-12-31T23:59:59.800Z', 1609459199.799999952316284 ],
[ '2020-12-31T23:59:59.900Z', 1609459199.900000095367432 ],
[ '2020-12-31T23:59:59.980Z', 1609459199.980000019073486 ],
[ '2020-12-31T23:59:59.990Z', 1609459199.990000009536743 ],
[ '2020-12-31T23:59:59.998Z', 1609459199.997999906539917 ],
[ '2020-12-31T23:59:59.999Z', 1609459199.999000072479248 ],
[ '2020-12-31T23:59:59.999800Z', 1609459199.999799966812134 ],
[ '2020-12-31T23:59:59.999900Z', 1609459199.999900102615356 ],
[ '2020-12-31T23:59:59.999980Z', 1609459199.999979972839355 ],
[ '2020-12-31T23:59:59.999990Z', 1609459199.999989986419678 ],
[ '2020-12-31T23:59:59.999998Z', 1609459199.999998092651367 ],
[ '2020-12-31T23:59:59.999999Z', 1609459199.999999046325684 ],
);
sub TRUE () { !!1 }
sub FALSE () { !!0 }
{ # Test UTC/GMT
foreach my $test (@tests) {
my ($exp, $epoch) = @$test;
my $got = epoch_to_datetime($epoch);
is($got, $exp, sprintf 'epoch_to_datetime(%.15f)', $epoch);
}
}
{ # Test +12:30 difference between local time and UTC/GMT of day
my $offset = +12 * 60 + 30;
no warnings 'redefine';
local *::_local_time_offset = sub { $offset };
foreach my $test (@tests) {
my ($exp, $epoch) = @$test;
$epoch -= $offset * 60;
$exp =~ s/Z$/\+12:30/ or die;
my $got = epoch_to_datetime($epoch, TRUE);
is($got, $exp, sprintf 'epoch_to_datetime(%.15f, TRUE)', $epoch);
}
}
{ # Test -12:30 difference between local time and UTC/GMT of day
my $offset = -12 * 60 - 30;
no warnings 'redefine';
local *::_local_time_offset = sub { $offset };
foreach my $test (@tests) {
my ($exp, $epoch) = @$test;
$epoch -= $offset * 60;
$exp =~ s/Z$/\-12:30/ or die;
my $got = epoch_to_datetime($epoch, TRUE);
is($got, $exp, sprintf 'epoch_to_datetime(%.15f, TRUE)', $epoch);
}
}
{ # Test Mojo::Date
require Mojo::Date;
foreach my $test (@tests) {
my ($exp, $epoch) = @$test;
$exp =~ s/0+Z$/Z/; # remove trailing zeros
my $got = Mojo::Date->new->epoch($epoch)->to_datetime;
is($got, $exp, sprintf 'Mojo::Date->new->epoch(%.15f)->to_datetime', $epoch);
}
}
}
done_testing();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment