Question

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!

Was it helpful?

Solution

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

OTHER TIPS

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.

You will probably have to explicitly tell the DateTime constructor which timezone(s) you are dealing with, because the three- and four-letter codes are not unique and not standardized.

I would suggest starting with DateTime::TimeZone, and possibly constructing new types for your timezones if you cannot find what you are looking for in the list. You may also find a DateTime formatter that conforms to your syntax (there are a huge number of them), but otherwise it's not difficult to simply use a regex to extract the date and time fields and pass that to a DateTime constructor.

Once you've got your strings parsed into DateTime objects with the times properly set, it's no problem at all to convert them back out to MySQL timestamps: just use DateTime::Format::MySQL:

my $string = DateTime::Format::MySQL->format_datetime($dt);

Try using the DateTime module.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top