3

I'm trying to localize times that are in UTC when the only thing I know about the destination time is the longitude and latitude. I've come up with something that works, but feels kludgy:

# We need to get localized time for display purposes.
state $moduleGeoLocation = require Geo::Location::TimeZone;
my $gltzobj = Geo::Location::TimeZone->new();
my $tzName = $gltzobj->lookup( lon => $params->{'longitude'}, lat => $params->{'latitude'} );
say "TimeZone: " . $tzName;

So far so good. Here's where the kludge comes in. I'm parsing a time using Time::Piece's strptime but I don't have a GMT offset for the timezone, so after I parse the UTC time in Time::Piece, I'm sending it over to DateTime to do the time zone calculation. It seems rather clunky to be using both DateTime and Time::Piece:

# Get TimeZoneOffset.
state $moduleDateTime = require DateTime;
state $moduleDateTimeTimeZone = require DateTime::TimeZone;
my $timeZone = DateTime::TimeZone->new( 'name' => $tzName );

my $timePiece = Time::Piece->strptime($hour->{'time'}, '%Y-%m-%dT%H:%M:%SZ');
my $time = DateTime->from_epoch( 'epoch' => $timePiece->epoch, 'time_zone' => $timeZone );

Is there a better way to accomplish what I'm doing? I'm aiming for the fastest possible way to get to the result of a localized time.

Timothy R. Butler
  • 1,097
  • 7
  • 20
  • There's [DateTime::Format::Strptime](https://metacpan.org/pod/DateTime::Format::Strptime), so no need for `Time::Piece`. Is that it? – zdim Apr 29 '21 at 21:03
  • Also, since Geo::Location::TimeZone returns time zones of the form `Etc/GMT+X`, it means Daylight-Saving Time won't be observed. – ikegami Apr 29 '21 at 21:51
  • @ikegami is there another module that you'd recommend instead that does as you say? That is pretty awful for your timezone. Although, I'm getting named zones out of G::L::TZ, not GMT+X. E.g. It is giving me "America/Chicago" for (CST/CDT), which should work with daylight savings time... – Timothy R. Butler Apr 29 '21 at 22:40
  • Finding time-zones for lon,lat with accuracy isn't simple. Other than the library used here, there is a good-looking list of tools in this [SO answer](https://stackoverflow.com/a/16086964/4653379) (not Perl but many are web services). I tried [google API](https://developers.google.com/maps/documentation/timezone/overview), which is really simple to use and I presume accurate. But it requires one to get an [API key](https://developers.google.com/maps/documentation/timezone/get-api-key) ... – zdim Apr 30 '21 at 03:52
  • Have a look at my project [here](https://github.com/simbabque/ingress-fs-finder/blob/master/bin/parse.pl), I'm doing roughly the same thing. I've found that Geo::Location::TimeZone is not very accurate. My input is all over the world, usually larger cities, and it often misses a few. So I only use that as a fallback now. – simbabque Apr 30 '21 at 08:26
  • 1
    @simbabque Perfect. Thank. I followed your lead and it really seems to be working better. GeoNames could almost replace my queries to OpenStreetMap, too, in addition to figuring out Time Zones although OSM seems to be a little more accurate in converting text strings to the most logical place possible, so I'm still querying OSM and then passing long/lat to the GeoNames API. Thanks for the pointer! – Timothy R. Butler May 06 '21 at 04:10

1 Answers1

3

Your question boils down to the following:

How do I create a DateTime object from a timestamp using the %Y-%m-%dT%H:%M:%S format. I have the appropriate time zone as a DateTime::TimeZone object.

To parse a date-time into a DateTime, one should first look for an appropriate DateTime::Format:: module.

DateTime::Format::Strptime would be the most similar to your current attempt.

use DateTime::Format::Strptime qw( );
 
my $format = DateTime::Format::Strptime->new(
   pattern   => '%Y-%m-%dT%H:%M:%S',
   strict    => 1,
   time_zone => $tz,
   on_error  => 'croak',
);

my $dt = $format->parse_datetime($ts);

You could also use DateTime::Format::ISO8601, although you couldn't use it as a validator since it doesn't accept only the stated format.

use DateTime::Format::ISO8601 qw( );
 
( my $dt = DateTime::Format::ISO8601->parse_datetime($ts) )
   ->set_time_zone('floating')
   ->set_time_zone($tz);

Given that the latter solution overrides any explicitly-provided time zone, I'd use the first solution for clarity.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Thanks! That works great. I should have gone with DateTime to begin with -- I decided to try using Time::Piece not realizing I'd be stuck when it came to timezones. – Timothy R. Butler Apr 29 '21 at 21:42
  • TZ conversions can be done using T::P. But I consider T::P to be a rather poor module. – ikegami Apr 29 '21 at 21:45
  • As @ikegami says, the conversions ar possible in Time::Piece, but it's very, very fragile. You have to get the stars to align just right. I just did this for a particular project that's stuck with core Perl, and that was not fun. – brian d foy Apr 29 '21 at 21:50
  • @brian d foy, I'm surprise at that. Well, I'm surprised if it was a Linux system. That would imply that time zones are fragile for all programs using the C library, which is basically all of them. – ikegami Apr 29 '21 at 21:55
  • Good to know. I thought I was missing out on something not trying T::P, but it doesn't sound like it. – Timothy R. Butler Apr 29 '21 at 22:41
  • It's more about what every different person puts in their TZ, which could be anything they copied off the internet. – brian d foy Apr 29 '21 at 23:50
  • @brian d foy, ah, sure. But that doesn't speak to the fragility of time zone conversion, but the difficulty of obtaining the user's time zone. Wouldn't that problem exist just as much for DateTime as Time::Piece? – ikegami Apr 30 '21 at 00:15
  • No idea there. Time::Piece relies on the system to provide various things. I thought DateTime had it's own timezone database. – brian d foy Apr 30 '21 at 00:16
  • @briandfoy, It does. It uses a fresh copy of the tz database rather than using the C library. But DateTime still uses the TZ env var. – ikegami Apr 30 '21 at 00:20
  • @briandfoy, Now, if you mean that people have bad data in their actual local time zone database, I heard of such problems, but I think that's a problem of the past (on Linux) – ikegami Apr 30 '21 at 00:23