views:

877

answers:

4

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!

A: 

Try using the DateTime module.

Vivin Paliath
@vivin - Can you provide a little more detail. I just added the code we've been using to the original question. Any idea why DateTime doesn't seem to be able to convert certain timezones?
Russell C.
I would store the data in UTC time. I'm not sure I understand your question though - do you want to know how to handle the insertion of dates into a MySQL database?
Vivin Paliath
I just updated the question to make it more clear. My question is basically can you provide a better perl implementation to convert the datetime provided by the API to the correct datetime format to be inserted into a MySQL datetime field.
Russell C.
I'd go with what Friedo said, regarding DateTime::Format::MySQL (and the timezones).
Vivin Paliath
+3  A: 

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.

friedo
We're currently using DateTime but for some reason it doesn't seem to be able to be able to handle certain time zones and is reporting errors for KDT, JDT, ICST, etc. I didn't know if there is a module with a little broader support?
Russell C.
I just added the code we're currently using the original question. Any ideas why DateTime doesn't seem to be able to handle certain timezones?
Russell C.
Unfortunately I don't know how to get it to support such timezones; they don't appear in the list of DateTime::TimeZone modules (http://search.cpan.org/~drolsky/DateTime-TimeZone-1.10/). You may need to convert them to an Olson-style name, like KDT => 'Asia/Seoul'
friedo
@friedo - Can you provide example Perl code to convert the datetime objects returned by the API to UTC format as you suggested? If possible I'd prefer to use a Perl module to parse the datetime input string instead of regex so it's a little more flexible.
Russell C.
To add support for additional time zones it looks like you will need to modify your `zoneinfo` file to add the missing time zones and let the DateTime-TimeZone tools compile the required modules to make a custom build. http://cpansearch.perl.org/src/DROLSKY/DateTime-TimeZone-1.10/tools/compile-all-zones
daotoad
+1  A: 

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);
Ether
@Ether - Thanks! Since the consensus seems to be that I need to store these dates in UTC format, can you provide some sample perl code to how I would do that once I have the proper DateTime object?
Russell C.
You don't have to store them in UTC, although you'll see a performance improvement if you are doing some math with the datetimes. However, what you do need to do is pass the correct timezone string to the DateTime constructor, e.g. `$my $dt = DateTime->new(..., timezone => 'America/Los_Angeles');`
Ether
+4  A: 

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
Greg Bacon
Russell C.
@gbacon - If there is a way to store the timezone in MySQL in a way that would allow me to do a query that would return the stored datetime (in GMT) in the local datetime with just a SQL query (no perl) that would probably be an even better solution.
Russell C.
@Russell Whose local datetime? The same as the time given by the API or the machine hosting the database?
Greg Bacon
@gbacon - The same as the time given by the API.
Russell C.
@Russell See updated answer.
Greg Bacon
@gbacon - Thanks for your help! While the end result we're using is a little different, it's based on your suggestions and seems to be working for all the items we're pulling through the API.
Russell C.
@Russell You're welcome! I'm glad my suggestions helped. If you'll humor one more suggestion, please make your question a more valuable resource to future searchers by editing it to describe your end result.
Greg Bacon