I'm trying to use the is_dst() method in the DateTime module to determine whether a daylight savings transition is occurring.
I started by writing a very basic example that I was sure would work, but for some reason I'm getting unexpected results.
Example:
#!/usr/bin/perl
use strict;
use warnings;
use DateTime;
my $dt1 = DateTime->new(
'year' => 2015,
'month' => 3 ,
'day' => 8 ,
'hour' => 3 ,
);
my $dt2 = DateTime->new(
'year' => 2015,
'month' => 3 ,
'day' => 7 ,
'hour' => 3 ,
);
print $dt1."\n";
print $dt2."\n";
print $dt1->is_dst()."\n";
print $dt2->is_dst()."\n";
I started with a date that I knew was a daylight savings transition: Sunday, March 8, 2015. I chose 3 AM because I knew that the daylight savings transition would have already occured at that point in time.
Then I took a date that I knew was before the daylight savings transition: Saturday, March 7, 2015 also at 3 AM.
Then I print the two dates and their corresponding DST flags.
Output:
2015-03-08T03:00:00
2015-03-07T03:00:00
0
0
The two dates print exactly as expected, but for some reason even though the first date occurs during daylight savings time, and the second date occurs before daylight savings time, the DST flag is not set for both of them.
Why is this not working correctly? Am I missing something?
You have to explicitly set the time zone:
The default time zone for new DateTime objects, except where stated otherwise, is the "floating" time zone...A floating datetime is one which is not anchored to any particular time zone.
For example:
use strict;
use warnings;
use DateTime;
my $dt1 = DateTime->new(
'year' => 2015,
'month' => 3,
'day' => 8,
'hour' => 3,
'time_zone' => 'America/New_York'
);
my $dt2 = DateTime->new(
'year' => 2015,
'month' => 3,
'day' => 7,
'hour' => 3,
'time_zone' => 'America/New_York'
);
print $dt1."\n";
print $dt2."\n";
print $dt1->is_dst()."\n";
print $dt2->is_dst()."\n";
Output:
2015-03-08T03:00:00
2015-03-07T03:00:00
1
0
Related
i am using the following code
DateFormat.yMMMMEEEEd("en_US").format((myDateTime)
that give me the following output (Date of writing my question)
Tuesday June 21 , 2022
well , now i don't want the previous format look like the previous output UNLESS if there is one week back as a maximum Period
for example i am looking for Format to Auto describe days of the week by (names) like following.
Assuming today is Tuesday
output => 'now' (if 5 mins as maximum ) else output => 'Today'
else:
output => 'yesterday' (if there one day back )
output => 'Sunday'
output => 'Saturday'
output => 'Friday'
output => 'Thursday'
output => 'Wednesday'
NOW finally we arrive to same day whish is Tuesday that means 1 week passed so i need output like my first format in my question
output => 'Tuesday June 21 , 2022'
And so
output => 'Tuesday June 21 , 2022'
output => 'Monday June 20 , 2022'
output => 'Sunday June 19 , 2022' ...... and so
in other words i only care of showing a week back in
their names only : now(if 5 mins as maximum) till A week back in
their names else DateFormat.yMMMMEEEEd("en_US").format((myDateTime)
what is the easiest and best way to do it . thanks
I am new to Perl and I am trying to figure out how to get the start and end date of the previous quarter. For example:
Jan 2nd, 2020 - The output should be 20191001, 20191231
July 27th, 2020 - The output should be 20200401, 20200630
Thanks in advance for all the help and guidance.
use DateTime qw( );
my $prev_quarter_start =
DateTime
->now( time_zone => 'local' )
->set_time_zone('floating') # Use this when dealing with dates.
->truncate( to => 'quarter' )
->subtract( months => 3 );
my $prev_quarter_end =
$prev_quarter_start
->clone
->add( months => 3 )
->subtract( days => 1 );
say $prev_quarter_start ->ymd('');
say $prev_quarter_end->ymd('');
Requires DateTime 1.32.
Background
I am using Perl to parse dates and datetimes input by users who aren't too careful with their formatting. The Perl module Date::Parse seems great because it handles most cases I need to handle.
Except datetimes between 1901-01-01 00:00:00 and 1968-12-31 23:59:59, as I found out today. For those datetimes, Date::Parse str2time adds an extra 100 years when it parses the datetime to epoch time.
Code
Here is the code I am using to parse the datetimes:
#!/usr/bin/perl
#---------------------------------------------------------------------
# format_date.pl
#
# format variable date inputs
#---------------------------------------------------------------------
use strict;
use warnings;
use Date::Parse;
use DateTime;
my $DEFAULT_TIME_ZONE = "GMT";
my #dates = (
"1899-06-24 09:44:00",
"1900-12-31 23:59:59",
"1901-01-01 00:00:00",
"1960-12-31 23:59:59",
"1966-06-24 09:44:00",
"1968-12-31 23:59:59",
"1969-01-01 00:00:00",
"1969-12-31 23:59:59",
"1970-01-01 00:00:01",
"2000-01-01 00:00:00",
"2017-06-24 23:59:59",
"2018-06-24 09:44:00",
"2238-06-24 09:44:00"
);
foreach my $string (#dates) {
# format datetime field from any valid datetime input
# default time zone is used if timezone is not included in string
my $epoch = str2time( $string, $DEFAULT_TIME_ZONE );
# error if date is not correctly parsed
if ( !$epoch ) {
die("ERROR ====> invalid datetime ($string), "
. "datetime format should be YYYY-MM-DD HH:MM:SS");
}
my $date = DateTime->from_epoch( epoch => $epoch );
printf( "formatting datetime: value = %20s, epoch = %20u, "
. "date = %20s\n", $string, $epoch, $date );
}
exit 0;
Side note: I need to improve my error handling because the valid date 1970-01-01 00:00:00 will throw an error.
Output
The additional 100 years for dates between 1901 and 1969 can be seen in the output:
formatting datetime: value = 1899-06-24 09:44:00, epoch = 18446744071484095456, date = 1899-06-24T09:44:00
formatting datetime: value = 1900-12-31 23:59:59, epoch = 18446744071532098815, date = 1900-12-31T23:59:59
formatting datetime: value = 1901-01-01 00:00:00, epoch = 978307200, date = 2001-01-01T00:00:00
formatting datetime: value = 1960-12-31 23:59:59, epoch = 2871763199, date = 2060-12-31T23:59:59
formatting datetime: value = 1966-06-24 09:44:00, epoch = 3044598240, date = 2066-06-24T09:44:00
formatting datetime: value = 1968-12-31 23:59:59, epoch = 3124223999, date = 2068-12-31T23:59:59
formatting datetime: value = 1969-01-01 00:00:00, epoch = 18446744073678015616, date = 1969-01-01T00:00:00
formatting datetime: value = 1969-12-31 23:59:59, epoch = 18446744073709551615, date = 1969-12-31T23:59:59
formatting datetime: value = 1970-01-01 00:00:01, epoch = 1, date = 1970-01-01T00:00:01
formatting datetime: value = 2000-01-01 00:00:00, epoch = 946684800, date = 2000-01-01T00:00:00
formatting datetime: value = 2017-06-24 23:59:59, epoch = 1498348799, date = 2017-06-24T23:59:59
formatting datetime: value = 2018-06-24 09:44:00, epoch = 1529833440, date = 2018-06-24T09:44:00
formatting datetime: value = 2238-06-24 09:44:00, epoch = 8472332640, date = 2238-06-24T09:44:00
Additional notes
The Date::Parse documentation suggests it can handle dates at least as old at 1901-01-01. The Time::Local documentation suggest it should be able handle dates even older.
Question
How should I handle this oddity? Is there a better way to parse variable input formats suing Perl?
Edit: examples of multiple date formats
Input can be in multiple formats. Here is an array of examples:
my #dates = (
"2018-02-20 00:00:00",
"20180220",
"02/20/2018",
"02/20/18", # interpreted as 1918-02-20
"2018-02-20"
);
The underlying issue was answered by tangent.
The problem is with Date::Parse - see this issue. Full answer on perlmonks – tangent
Solution 1
My solution is to use Date::Parse strptime instead of str2time.
Date::Parse strptime parse the date into an array ( $ss, $mm, $hh, $day, $month, $year, $zone ). That allows the year to be converted back to a 4-digit year using:
if ( $year < 1000 ) { $year += 1900; }
The date is then passed into DateTime->new().
Solution 2 (better)
Based on discussion with thanos on perlmonks, I explored using the Date::Manip module to parse datetimes. This simplified parsing variable inputs to as little as one line. It even handles 2-digit years correctly. Here is a snippet of the code:
say UnixDate( ParseDate($_), '%Y-%m-%d %T' ) for (#dates);
See example scripts and output on perlmonks.
Just to add another possible solution using module Date::Manip.
use Date::Manip;
use use feature 'say';
foreach my $datestr (#dates) {
my $epochSecs = UnixDate($datestr,'%s');
my $date = UnixDate( ParseDateString("epoch $epochSecs"), "%Y-%m-%d %T");
say "Date value = ".$datestr.", epoch = ".$epochSecs.", date = " .$date;
}
Hope this helps, BR.
Epoch time is the number of seconds since 1970-01-01T00:00:00Z. The date your are trying to convert to epoch time is earlier than this.
Why are you using two different date-time libraries? If you want a DateTime object, use a DateTime module.
use DateTime::Format::DateParse qw( );
for my $dt_str (#dates) {
my $dt = DateTime::Format::DateParse->parse_datetime($dt_str, $DEFAULT_TIME_ZONE)
or die(...);
...
}
Produces:
1899-06-24 09:44:00 => 3799-06-24T09:44:00 <- doh!
1900-12-31 23:59:59 => 3800-12-31T23:59:59 <- doh!
1901-01-01 00:00:00 => 1901-01-01T00:00:00
1960-12-31 23:59:59 => 1960-12-31T23:59:59
1966-06-24 09:44:00 => 1966-06-24T09:44:00
1968-12-31 23:59:59 => 1968-12-31T23:59:59
1969-01-01 00:00:00 => 1969-01-01T00:00:00
1969-12-31 23:59:59 => 1969-12-31T23:59:59
1970-01-01 00:00:01 => 1970-01-01T00:00:01
2000-01-01 00:00:00 => 2000-01-01T00:00:00
2017-06-24 23:59:59 => 2017-06-24T23:59:59
2018-06-24 09:44:00 => 2018-06-24T09:44:00
2238-06-24 09:44:00 => 2238-06-24T09:44:00
2018-02-20 00:00:00 => 2018-02-20T00:00:00
20180220 => 2018-02-20T00:00:00
02/20/2018 => 2018-02-20T00:00:00
02/20/18 => 1918-02-20T00:00:00
2018-02-20 => 2018-02-20T00:00:00
Let's avoid DateParse entirely.
use DateTime::Format::Strptime qw( );
use List::MoreUtils qw( first_result );
my #patterns = (
'%Y-%m-%d %H:%M:%S',
'%Y-%m-%d',
'%Y%m%d',
'%m/%d/%Y',
'%m/%d/%y',
);
my #formats =
map {
DateTime::Format::Strptime->new(
pattern => $_,
time_zone => $DEFAULT_TIME_ZONE,
on_error => 'undef',
)
}
#patterns;
for my $dt_str (#dates) {
my $dt = first_result { $_->parse_datetime($dt_str) } #formats
or die(...);
...
}
Produces:
1899-06-24 09:44:00 => 1899-06-24T09:44:00
1900-12-31 23:59:59 => 1900-12-31T23:59:59
1901-01-01 00:00:00 => 1901-01-01T00:00:00
1960-12-31 23:59:59 => 1960-12-31T23:59:59
1966-06-24 09:44:00 => 1966-06-24T09:44:00
1968-12-31 23:59:59 => 1968-12-31T23:59:59
1969-01-01 00:00:00 => 1969-01-01T00:00:00
1969-12-31 23:59:59 => 1969-12-31T23:59:59
1970-01-01 00:00:01 => 1970-01-01T00:00:01
2000-01-01 00:00:00 => 2000-01-01T00:00:00
2017-06-24 23:59:59 => 2017-06-24T23:59:59
2018-06-24 09:44:00 => 2018-06-24T09:44:00
2238-06-24 09:44:00 => 2238-06-24T09:44:00
2018-02-20 00:00:00 => 2018-02-20T00:00:00
20180220 => 2018-02-20T00:00:00
02/20/2018 => 2018-02-20T00:00:00
02/20/18 => 2018-02-20T00:00:00
2018-02-20 => 2018-02-20T00:00:00
#! /usr/bin/perl
use DBI;
use strict;
use Data::Dumper;
my $dbh = DBI->connect("DBI:DB2:xxx",'xxx','xxx',{ RaiseError => 0, AutoCommit => 1 })
or die ("Could not connect to database :".DBI->errstr);
my %hash =
(
'2017-01-01 00:00:00' => '2017-01-31 00:00:00',
'2017-02-01 00:00:00' => '2017-02-28 00:00:00',
'2017-03-01 00:00:00' => '2017-03-31 00:00:00',
'2017-04-01 00:00:00' => '2017-04-30 00:00:00',
'2017-05-01 00:00:00' => '2017-05-31 00:00:00',
'2017-06-01 00:00:00' => '2017-06-30 00:00:00',
'2017-07-01 00:00:00' => '2017-07-31 00:00:00',
'2017-08-01 00:00:00' => '2017-08-31 00:00:00',
'2017-09-01 00:00:00' => '2017-09-30 00:00:00'
);
#open(my $fh , "+>/var/www/bin/filesample.txt");
foreach my $key(sort keys %hash) {
chomp($key);
#my $sql = "select distinct FID_CUST from session where DAT_END between ? and ?";
my $sql = "select distinct FID_CUST from session where DAT_END between TIMESTAMP(?) and TIMESTAMP(?)";
print "\$sql = $sql\n";
my $sth = $dbh->prepare($sql);
$sth->execute($key,$hash{$key}) or die "Couldn't execute statement: $DBI::errstr";
print "sth: $sth\n";
while (my $arr = $sth->fetchrow_arrayref()){
print "in while\n";
print "#$arr\n";
}
$sth->finish();
}
#close FH;
$dbh->disconnect;
Here, i am not able to get what is wrong with the code as control is not going to while loop. Please suggest what can be done. I am using DB2 database. I am not getting any error but it simply won't show any output. I have used TIMESTAMP also with placeholders but it is showing some ambiguity error.
The documentation for TIMESTAMP says:
A character string or graphic string with an actual length of 14 that represents a valid date and time in the form yyyyxxddhhmmss, where yyyy is the year, xx is the month, dd is the day, hh is the hour, mm is the minute, and ss is the seconds.
You are passing strings of actual length 19 such as '2017-01-01 00:00:00'.
Pass timestamps formatted in the way DB2 expects them, e.g. 20170101000000.
If that did not work, then try the advice given in DB2 documentation for error SQL0245N
This error is returned when an invocation of a function is ambiguous. This occurs when there are two or more possible candidate functions that satisfy the criteria for function resolution.
...
User response
Change the SQL statement to explicitly cast the argument to the desired data type, the definition of a function, or the SQL path to remove the ambiguity from the set of candidate functions and try again.
and change your query to:
select distinct FID_CUST
from session
where DAT_END between
TIMESTAMP(cast(? as TIMESTAMP)) and
TIMESTAMP(cast(? as TIMESTAMP))
or similar.
As I said, your question does not have any Perl content. If the syntax I concocted for casting strings to TIMESTAMPs is incorrect, ask your database admin for the correct DB2 SQL syntax.
Change all your timestamp literals from their current format
'2017-01-01 00:00:00' => '2017-01-31 00:00:00',
into
'2017-01-01-00.00.00.000000' => '2017-01-31-00.00.00.000000',
Consider the following code snippet that takes user input (a date) and format it using UnixDate from Date::Manip
#!/usr/bin/perl
use Date::Manip;
my $input = join(" ", #ARGV);
my $date = UnixDate($input, "%Y-%m-%d %T");
print $date;
This was done to allow users to enter friendly dates such as "yesterday" or "1 week ago".
I would like to use $date with a different timezone (it will be used to extract SQL data). How would this be done?
I did not find any construct of UnixDate that would allow to put a timezone, and I do not know either how to reformat the user input (concatenating the name of the timezone to it doesn't help).
Example
The user is somewhere in Central Europe (timezone: CET) and enters "today at 1pm". Execution of the code above is as follows:
$ ./test.pl today at 1pm
2011-03-03 13:00:00
This is the expected result as no timezone change are in effect. What I would like is to use that $date with another timezone, e.g. Pacific Standard (timezone: PST). In this case the output should be:
$ ./test.pl today at 1pm
2011-03-03 04:00:00
Try the Date_ConvTZ() function:
my $date = UnixDate( Date_ConvTZ( $input, 'CET', 'PST' ), "%Y-%m-%d %T");
From the manual from Date::Manip::DM6
I don't know how to make Date::Manip understand timezones but this would be pretty straight forward with DateTime:
my $input = join(" ", #ARGV);
my $date = UnixDate($input, "%Y-%m-%d %T");
$date =~ /(\d\d\d\d)-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)/;
my $dt = DateTime->new(
year => $1,
month => $2,
day => $3,
hour => $4,
minute => $5,
second => $6,
time_zone => 'CET',
);
$dt->set_time_zone('America/Vancouver'); # My DateTime::TimeZone doesn't have PST
You might be able to replace your Date::Manip uses with one or more of the DateTime modules too, DateTime is the standard date manipulation library for Perl so using it for all your date-time needs makes sense; OTOH, use what works for you and there's probably no harm in using both Date::Manip and DateTime if that gets the job done.