Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert Between Datetime Formats Using Perl

We're currently using a 3rd party API that provides datetime in the following format:

Sat Mar 06 09:00:00 ICST 2010
Fri Feb 19 19:30:00 JDT 2010
Fri Feb 19 19:30:00 PST 2010

However, we want to store these datetime objects in MySQL in a standard datetime field which requires the following format:

YYYY-MM-DD HH:MM:SS

Right now we're using the following code which is reporting errors for certain timezones such as KDT, JDT, and ICST:

use Date::Manip;
use DateTime;
use DateTime::Format::DateManip;

my $date = ParseDate($time);
$date = DateTime::Format::DateManip->parse_datetime($date);
eval{ $time = $date->strftime("%Y-%m-%d %H:%M:%S"); };

Can you recommend a better implementation of the Perl code above to convert the datetime objects from the API to the proper format and time on our server to be inserted into a MySQL datetime field?

Thanks in advance for your help & advice!

like image 242
Russell C. Avatar asked Feb 12 '10 15:02

Russell C.


2 Answers

Store the times internally in GMT. Do all manipulations in GMT. Then at the last moment, just as you're about to display results to the user, then convert to the user's local time.

I recommend using Date::Parse, but you'll have to augment its timezone offsets because it doesn't currently have Indochina Summer Time and Japan Daylight Time, for example.

#! /usr/bin/perl

use warnings;
use strict;

use Date::Format;
use Date::Parse;

# add timezone offsets
$Time::Zone::Zone{icst} = +7*3600;
$Time::Zone::Zone{jdt}  = +9*3600;

while (<DATA>) {
  chomp;
  warn("$0: failed conversion for $_\n"), next
    unless defined(my $time_t = str2time $_);

  my @t = gmtime($time_t);
  print $_, " => ", strftime("%Y-%m-%d %H:%M:%S", @t), "\n";
}

__DATA__
Sat Mar 06 09:00:00 ICST 2010
Fri Feb 19 19:30:00 JDT 2010
Fri Feb 19 19:30:00 PST 2010

Output:

Sat Mar 06 09:00:00 ICST 2010 => 2010-03-06 02:00:00
Fri Feb 19 19:30:00 JDT 2010 => 2010-02-19 10:30:00
Fri Feb 19 19:30:00 PST 2010 => 2010-02-20 03:30:00

To support the query you'd like, store the time in GMT plus an offset (i.e., from GMT to the local time from the API). Note that the code below assumes that if str2time can parse a given time, strptime can also. Change the loop to

my @dates;
while (<DATA>) {
  chomp;
  warn("$0: failed conversion for $_\n"), next
    unless defined(my $time_t = str2time $_);

  my $zone = (strptime $_)[-1];
  my @t = gmtime($time_t);
  push @dates => [ strftime("%Y-%m-%d %H:%M:%S", @t)
                 , sprintf("%+03d:%02d",
                           int($zone / 3600),
                           int($zone % 3600) / 60)
                 , $_
                 ];
}

With the times collected, render it as SQL:

print "DROP TABLE IF EXISTS dates;\n",
      "CREATE TABLE dates (date DATETIME, offset CHAR(6));\n",
      "INSERT INTO dates (date,offset) VALUES\n",
        join(",\n\n" =>
          map("  -- $_->[2]\n" .
              "  ('$_->[0]','$_->[1]')", @dates)),
        ";\n",
      "SELECT CONVERT_TZ(date,'+00:00',offset) FROM dates;\n"

The output is

DROP TABLE IF EXISTS dates;
CREATE TABLE dates (date DATETIME, offset CHAR(6));
INSERT INTO dates (date,offset) VALUES
  -- Sat Mar 06 09:00:00 ICST 2010
  ('2010-03-06 02:00:00','+07:00'),

  -- Fri Feb 19 19:30:00 JDT 2010
  ('2010-02-19 10:30:00','+09:00'),

  -- Fri Feb 19 19:30:00 PST 2010
  ('2010-02-20 03:30:00','-08:00');
SELECT CONVERT_TZ(date,'+00:00',offset) FROM dates;

and we can pipe it to mysql:

$ ./prog.pl | mysql -u username -D dbname
CONVERT_TZ(date,'+00:00',offset)
2010-03-06 09:00:00
2010-02-19 19:30:00
2010-02-19 19:30:00
like image 142
Greg Bacon Avatar answered Sep 30 '22 09:09

Greg Bacon


When storing dates, you should always store them in UTC. That way, you can fetch them from the database and convert them to the appropriate timezone as necessary for display to the user.

For handling datetimes properly in Perl, I heartily recommend the DateTime suite, which has parsers and formatters for all sorts of various input and output formats.

I'm not sure if the dates listed in your OP are a standard format, but if not, it would be pretty easy to construct a DateTime format from them:

my $str = 'Sat Mar 06 09:00:00 ICST 2010';
my ( $month, $day, $hour, $min, $sec, $tz, $year ) = ( $str =~ m{\w{3}\s(\w{3})\s(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s(\w+)\s(\d{4})} );

my $dt = DateTime->new( year => $year, month => $month, day => $day, 
                        hour => $hour, minute => $min, second => $sec, 
                        time_zone => $tz );

You could then use DateTime::Format::MySQL to convert the date to a MySQL-compatible datetime.

like image 28
friedo Avatar answered Sep 30 '22 09:09

friedo