Force Time::Piece strftime to use a specific locale - perl

How can Time::Piece->strftime be forced to output weeks' and months' names according to a specific locale, and not according to the current locale?
Setting $ENV{LC_ALL} to a specific locale (say, C.UTF-8) does not seem to work.
The following test script
#!/usr/bin/env perl
$ENV{LC_ALL} = "C.UTF-8";
use strict;
use warnings;
use Time::Piece;
my $s1 = "Mon, 26 Sep 2022";
my $format = "%a, %d %b %Y";
my $d = Time::Piece->strptime($s1, $format);
my $s2 = $d->strftime($format);
if ($s1 eq $s2) {
print("OK: '$s1' == '$s2'\n");
} else {
die("ERROR: '$s1' != '$s2'\n");
}
fails when run in, for example, a fr_CH.UTF-8 environment:
$ LC_ALL=fr_CH.UTF-8 ./test-strftime.pl
ERROR: 'Mon, 26 Sep 2022' != 'lun, 26 sep 2022'

Perl relies on glibc to expand these formats, and it only initialises the locale definitions during startup. You can force re-initialization during runtime with POSIX::setlocale() to a locale of your choosing, but beware this is a truly global action and prior to Perl 5.28 it may even impact other threads.
Example (instead of $ENV{LC_ALL} = "C.UTF-8"):
use POSIX qw(locale_h);
setlocale(LC_ALL, "C.UTF-8");

Related

How to format date to "2018-10-29 11:49:33"

I have to convert the GMT date to region specific date with format like "YYYY-MM-DD H:M:S".
Code developed is :-
use Time::Local;
($year,$mon,$day) = split /\-/, $ARGV[0];
($hrs,$min,$sec ) = split /:/, $ARGV[1];
$time = timegm( $sec, $min, $hrs, $day, $mon-1, $year-1900);
print scalar localtime($time), "\n";
But when I run it like :-
$ perl testDateGMTToLocal.pl 2018-10-29 11:49:33
It gives o/p converted in local time zone:-
Mon Oct 29 07:49:33 2018
But I want this o/p in below format
29-OCT-18 07:49:33
Thanks in advance.
I'd recommend to do it all using modules. The all-capable and very complete module is DateTime, and for this job you'd also need DateTime::Format::Strptime.
One other option is the simpler and much smaller core module Time::Piece
use warnings;
use strict;
use feature 'say';
use Time::Piece;
die "Usage $0 YYYY-MM-DD H:M:S" if #ARGV != 2;
my $time = join ' ', #ARGV;
my $tp = Time::Piece->strptime($time, "%Y-%m-%d %T");
my $local = localtime($tp->epoch);
say $local;
# In the desired format
say join('-', $local->mday, uc $local->month, $local->yy),
' ', $local->hms;
# If "Oct" is ok instead of block capitals for month abbreviation
say $local->strftime("%d-%b-%y %T");
This converts GMT time, with invocation as in the question, to the local time on my machine
Mon Oct 29 04:09:33 2018
29-OCT-18 04:09:33
29-Oct-18 04:09:33
where the middle one was asked for.
On some systems there is the %F format specifier for %Y-%m-$d.ā€  There may be a defined format for 29-OCT-18, in which case you don't have to patch it by hand, but I am not aware of it.
ā€  Or the module has its own formatting in which case that's portable. But origin of the error when it fails to do %F on my system isn't clear to me in that sense.
You can use
use POSIX qw( strftime );
print(strftime("%d-%b-%y %H:%M:%S", localtime($time)), "\n");

convert perl timestamp to human readable

I am reading a log file which contains time stamps which I want to convert to human readable.
In this command, $1 contains a time stamp (like this 1403457192.663): $temp = localtime->mon($1) but instead of storing the month, $temp contains the same timestamp that was input. What am I doing wrong?
You're close. The time should be passed to the localtime function, not the mon method.:
$temp = localtime($1)->mon; # 6
You can use strftime with this to turn it into any arbitrary format
localtime($1)->strftime("%b %d %a"); # Jun 22 Sun
Or if you're not picky about the format you can just stringify it:
$temp = localtime($1);
print "$temp\n"; # Sun Jun 22 13:13:12 2014
This assumes that Time::Piece is loaded.
I'd simply go with
$ perl -E'
use POSIX qw( strftime );
say strftime("%Y/%m/%d %H:%M:%S", localtime(1403457192.663));
'
2014/06/22 13:13:12
But you're using Time::localtime. That module overrides the localtime builtin, so you need a slight modification if you use that.
Either avoid using Time::localtime's localtime
$ perl -E'
use POSIX qw( strftime );
use Time::localtime qw( localtime );
say strftime("%Y/%m/%d %H:%M:%S", CORE::localtime(1403457192.663));
'
2014/06/22 13:13:12
or flatten an existing Time::localtime object.
$ perl -E'
use POSIX qw( strftime );
use Time::localtime qw( localtime );
my $tm = localtime(1403457192.663);
say strftime("%Y/%m/%d %H:%M:%S", #$tm);
'
2014/06/22 13:13:12
All of these solutions lose the millisecond precision. If it's relevant, you'll have to extract it from the original input and reinsert it in the output.
For formatting dates most system strftime manual pages will list a few "shortcuts" to get you certain "standard" formats.
e.g. %F is equivalent to ā€œ%Y-%m-%dā€.
~/% perl -MPOSIX -E'say strftime"%D",localtime'
06/25/14
~/% perl -MPOSIX -E'say strftime"%F",localtime'
2014-06-25
These can make using "ye olde" strftime easier ;-)
Perl since 5.10 now contains Time::Piece. This makes it the official way to handle time in Perl. Or, about as official as something gets in Perl. Since it's always available, you might as well learn to use that:
use strict;
use warnings;
use Time::Piece;
use Time::Seconds; # More time fun!
my $time = Time::Piece->new; # Gets the current timestamp
my $month = $time->mon(); # Month from 1 to 12
my $month = $time->month(); # Abbreviation of the name of month
my $month = $time->fullmonth(); # Full name of the month
my $time = $time + (ONE_DAY * 30) # Add thirty days to the time
my $date = $time->mdy # The date 30 days from now.

perl print current year in 4 digit format

how do i get the current year in 4 digit this is what i have tried
#!/usr/local/bin/perl
#months = qw( Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec );
#days = qw(Sun Mon Tue Wed Thu Fri Sat Sun);
$year = $year+1900;
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
print "DBR_ $year\\$months[$mon]\\Failures_input\\Failures$mday$months[$mon].csv \n";
This prints DBR_ 114\Apr\Failures_input\Failures27Apr.csv
How do I get 2014?
I am using version 5.8.8 build 820.
use Time::Piece;
my $t = Time::Piece->new();
print $t->year;
Move the line:
$year = $year+1900;
To after that call to localtime() and to become:
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime();
$year = $year+1900;
The best way is to use the core library Time::Piece. It overrides localtime so that the result in scalar context is a Time::Piece object, you can use the many methods that the module supplies on it. (localtime in list context, as you have used it in your own code, continues to provide the same nine-element list.)
The strftime method allows you to format a date/time as you wish.
This very brief program produces the file path that I think you want (I doubt if there should be a space after DBR_?) Note that there is no need to double up backslashes inside a single-quoted string unless it is the last character of the string.
use strict
use warnings;
use Time::Piece;
my $path = localtime->strftime('DBR_%Y\%b\Failures_input\Failures%m%d.csv');
print $path;
output
DBR_2014\Apr\Failures_input\Failures27Apr.csv
One option to get the 4 digit year:
#!/usr/bin/perl
use POSIX qw(strftime);
$year = strftime "%Y", localtime;
printf("year %02d", $year);
You can also use
my ($y,$m,$d) = Date::Calc::Today();
$y variable will contain 2019
$m variable will contain 8
$d variable will contain 9
at the time of writing this answer ( 9th August 2019 )
The simplest way, I find, to get the year is:
my $this_year = (localtime)[5] + 1900;

How can I convert a date time and time zone correctly?

I have done some searching and I'm not sure how to get this converted correctly using core Perl modules. I have 2013-10-22T19:31:00Z and want to get 10/22/2013 and the time in US Central time.
Here's a solution using just core modules (well, you need to have at least perl 5.8):
use strict;
use POSIX qw(tzset strftime);
use Time::Local qw(timegm);
my $iso_time = "2013-10-22T19:31:00Z";
my $formatted_time;
{
local $ENV{TZ} = "America/Chicago";
tzset;
if (my($y,$m,$d,$H,$M,$S) = $iso_time =~ m{^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z$}) {
$formatted_time = strftime '%m/%d/%Y %H:%M:%S', localtime timegm $S,$M,$H,$d,$m-1,$y;
} else {
# invalid ISO date, do something
}
}
tzset; # restore default time zone handling
warn $formatted_time, "\n"; # output is "10/22/2013 14:31:00"
A word about the TZ trick: by setting the environment variable TZ some time-related libc functions (e.g. localtime) use that time zone instead of the default time zone. If the TZ environment variable is set within a program, then tzset() has to be called to adjust the internal time-related data structures. And don't forget to call tzset() again to restore the data structures again.
This should work on most Unix system, but may or may not work on a Windows system.
use DateTime::Format::RFC3339 qw( );
use DateTime::Format::Strptime qw( );
my $in_dt_format = DateTime::Format::RFC3339->new();
my $out_dt_format = DateTime::Format::Strptime->new(
pattern => '%m/%d/%Y %H:%M:%S',
on_error => 'croak',
);
my $dt = $in_dt_format->parse_datetime('2013-10-22T19:31:00Z');
$dt->set_time_zone('America/Chicago');
say $out_dt_format->format_datetime($dt); # 10/22/2013 14:31:00
Standard Perl modules?
That would include Time::Piece that's been part of Perl since 5.10.
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
use Time::Piece;
my $date = "2013-10-22T19:31:00Z";
my $time = Time::Piece->strptime ( $date, "%Y-%m-%dT%H:%M:%SZ" );
$time = localtime($time); # Converts time to local time
say $time->mdy("/");
This prints out:
10/22/2013
That wacky "%Y-%m-%dT%H:%M:%SZ" string represents the format of your time input. You can find it in your manage for strftime.
%Y: Four digit year
%m: Month from 01 - 12
%d: Day from 01 - 31
%H: Hour from 00 - 23
%M: Minute from 00 - 59
%S: Second from 00 - 59

Simple way to format date

I wrote a perl script to get datetime.
It do work but I do wonder if there any easier way to format date as output.
#!/usr/bin/perl
use DateTime;
my $dt = DateTime->now( time_zone => 'local' );
$myTimeStamp = $dt->subtract( days => 1 );
$myYear = $myTimeStamp->year;
$myMonth = $myTimeStamp->month;
if ( length( $myMonth ) == 1 ) {
$myMonth = "0$myMonth";
}
$myDay = $myTimeStamp->day;
if ( length( $myDay ) == 1 ) {
$myDay = "0$myDay";
}
$myHour = $myTimeStamp->hour;
if ( length( $myHour ) == 1 ) {
$myHour = "0$myHour";
}
#$myDir = "/var/tmp/logs/$myYear/$myMonth/$myYear$myMonth-";
print "--> $myYear $myMonth $myDay $myHour\n";
# --> 2012 02 28 02
exit 0;
DateTime provides the format_cldr method for this:
use DateTime;
my $myTimeStamp = DateTime->now->subtract( days => 1 );
printf "--> %s\n", $myTimeStamp->format_cldr('yyyy MM dd HH');
# --> 2012 02 28 02
Sure, use POSIX module:
The POSIX module permits you to access all (or nearly all) the
standard POSIX 1003.1 identifiers.
Example:
use POSIX;
print POSIX::strftime('%d-%m-%Y %H:%M:%S', localtime());
First of all always use strict; and use warnings; at the start of your program and declare all your variables close to their first use. This applies especially if you are seeking help as it will find a lot of simple errors that aren't immediately obvious.
It is best to use printf if you want to zero-pad any output. There is also no need to extract the date fields to separate variables. Is the output you have shown the one you ultimately want? This program does the same thing as the code you have posted.
use strict;
use warnings;
use DateTime;
my $myTimeStamp = DateTime->now->subtract( days => 1 );
printf "--> %04d %02d %02d %02d\n", map $myTimeStamp->$_, qw/year month day hour/;
OUTPUT
--> 2012 02 28 12
For re-formatting dates, as noted, there is the POSIX core module. You would be remiss not to look at the core module Time::Piece too, which not only delivers strftime() but also strptime() to provide very flexible date/time parsing. Time::Piece appeared in Perl core in 5.9.5.