How to make regional date formats using Excel::Writer::XLSX - perl

I use the Excel::Writer::XLSX module to generate excel report, but the date format does not change according to different regions.
For example, if I set the region to 'English-United States', the date format should be 'mm/dd/yyyy' shown on excel sheet, if I reset the region to 'French-France', the date format should be 'dd/mm/yyyy'.
So I'd like to know how it is implemented using Excel::Writer::XLSX. Thanks.

Excel stores a limited number of date formats with regional settings. For example in the number format dialog in Excel you will see a warning like this:
Displays date and time serial numbers as date values, according to the type and locale (location) that you specify. Date formats that begin with an asterisk (*) respond to changes in regional date and time settings that are specified in Control Panel. Formats without an asterisk are not affected by Control Panel settings.
In Excel::Writer::XLSX you can set these using a num_format id number instead of a format string. For example:
#!/usr/bin/perl
use strict;
use warnings;
use Excel::Writer::XLSX;
my $workbook = Excel::Writer::XLSX->new( 'dates.xlsx' );
my $worksheet = $workbook->add_worksheet( 'Demo' );
$worksheet->set_column('A:A', 20);
# Regional date format.
my $date_format_1 = $workbook->add_format( num_format => 14 );
# Non-regional date format.
my $date_format_2 = $workbook->add_format( num_format => 'dd/mm/yy' );
$worksheet->write_date_time( 'A1', '2013-10-27T', $date_format_1 );
$worksheet->write_date_time( 'A2', '2013-10-27T', $date_format_2 );
__END__
See the num_format section of the docs for more examples of the builtin format ids.

Related

Perl Excel::Writer::XLSX correct syntax for the Excel function Move Area.function

I use the Perl module Excel::Writer::XLSX. If I enter the function directly in excel under data check it works. The modified form for the Perl script does not work.
I have some sheets in my Excel file. Under 'data validation' -> 'list', I use this as 'source':
=BEREICH.VERSCHIEBEN(Boden_Subtyp!$E:$G;1;VERGLEICH(Profil!$G$2;Boden_Subtyp!$E$1:$G$1;0)-1;ANZAHL2(INDEX(Boden_Subtyp!$E:$G;;VERGLEICH(Profil!$G$2;Boden_Subtyp!$E$1:$G$1;0)));1)
It works.
If I use this in a modified form in my Perl script to create an Excel file with this function so I can't open the Excel file. The modified form:
=BEREICH.VERSCHIEBEN('Boden_Subtyp'!$E:$G;1;VERGLEICH('Profil'!$G$2;'Boden_Subtyp'!$E$1:$G$1;0)-1;ANZAHL2(INDEX('Boden_Subtyp'!$E:$G;;VERGLEICH('Profil'!$G$2;'Boden_Subtyp'!$E$1:$G$1;0)));1)
Code snipped:
$validate_source = "=BEREICH.VERSCHIEBEN('Boden_Subtyp'!$E:$G;1;VERGLEICH('Profil'!$G$2;'Boden_Subtyp'!$E$1:$G$1;0)-1;ANZAHL2(INDEX('Boden_Subtyp'!$E:$G;;VERGLEICH('Profil'!$G$2;'Boden_Subtyp'!$E$1:$G$1;0)));1)";
$profil_zugriffshash -> data_validation(
$iii,$spaltenzaehler,
{
validate => 'list',
source => "$validate_source",
}
);
If the Excel file is created I'll open it with Excel and get the error: excel's unreadable content was found ...
From the Excel::Writer::XLSX docs but repeated here for clarity:
Non US Excel functions and syntax
Excel stores formulas in the format of the US English version, regardless of the language or locale of the end-user's version of Excel. Therefore all formula function names written using Excel::Writer::XLSX must be in English:
worksheet->write_formula('A1', '=SUM(1, 2, 3)'); # OK
worksheet->write_formula('A2', '=SOMME(1, 2, 3)'); # French. Error on load.
Also, formulas must be written with the US style separator/range operator which is a comma (not semi-colon). Therefore a formula with multiple values should be written as follows:
worksheet->write_formula('A1', '=SUM(1, 2, 3)'); # OK
worksheet->write_formula('A2', '=SUM(1; 2; 3)'); # Semi-colon. Error on load.
If you have a non-English version of Excel you can use the following multi-lingual Formula Translator (http://en.excel-translator.de/language/) to help you convert the formula. It can also replace semi-colons with commas.
Using the translator listed above the formula should be:
OFFSET('Boden_Subtyp'!$E:$G,1,MATCH('Profil'!$G$2,'Boden_Subtyp'!$E$1:$G$1,0)-1,COUNTA(INDEX('Boden_Subtyp'!$E:$G,,MATCH('Profil'!$G$2,'Boden_Subtyp'!$E$1:$G$1,0))),1)
That's not the solution to the problem. I simplify the problem. The script creates a file that Excel can read without errors. If the commented source entry is used, Excel reports an error. However, the commented source entry can be directly exchanged for the uncommented source entry in Excel.
#!/usr/bin/perl -w
use diagnostics;
use strict;
use warnings;
use Excel::Writer::XLSX;
my $workbook = Excel::Writer::XLSX->new( '/home/nutzer/test_bereich_verschieben.xlsx' );
my $home_hash = $workbook->add_worksheet('Home');
$home_hash -> write(0, 0, 'range_val');
$home_hash -> data_validation(
1,0,
{
validate => 'list',
source => 'Horizont!$C$5:$C$6', # list: 2 and 3
# source => '=BEREICH.VERSCHIEBEN(Horizont!$C:$E;3;0;3;1)', # list: 1 and 2 and 3
}
);
my $horizont_hash = $workbook->add_worksheet('Horizont');
$horizont_hash -> write(3, 2, '1');
$horizont_hash -> write(4, 2, '2');
$horizont_hash -> write(5, 2, '3');
$workbook->close;
exit;
__END__

Output timezone with Matlab datestr()

Very simple question. I'm using Matlab's datetime type, so I can carry timezone information. I need to get a specific string representation, to input into a DB. But datestr() does not have any fields to output tz info.
a = datetime('now', 'TimeZone', 'UTC');
%need output in the format 'YYYYMMDDTHH:MM:SS+00:00'
Any thoughts?
You can get the output you want by setting the Format property of the datetime object to display the time zone offset, converting it to a character array, then replacing the space by 'T':
>> a = datetime('now', 'TimeZone', 'UTC', 'Format', 'yyyyMMdd HH:mm:SSxxxxx')
a =
datetime
20171002 21:37:74+00:00
>> out = strrep(char(a), ' ', 'T')
out =
20171002T21:37:74+00:00
Also, take note of the case of the letters in the format string, as that matters for some of them.

How do I set a workbook-wide default format with Excel::Writer::XLSX?

Is it possible to change or declare a default font with Excel::Writer::XLSX?
I can set it for charts and cells every time I create or add them. But I think there should be a more simple way.
Maybe.
Disclaimer: the author of Excel::Writer::XLSX explains this is not a good idea and is not guaranteed to work here and here.
You can set the property xf_index to 0 when creating a Format object. That will set that Format as the default one.
use Excel::Writer::XLSX;
my $workbook = Excel::Writer::XLSX->new( 'filename.xlsx' );
# set the default format
$workbook->add_format(xf_index => 0, font => 'Comic Sans MS' ); # I'm on Linux
my $worksheet = $workbook->add_worksheet();
$worksheet->write( 0, 0, 'Hi Excel!' ); # will be in Comic Sans
my $format = $workbook->add_format(font => 'Arial' );
$worksheet->write( 1, 1, 'In Arial!', $format );
This is not described in the docs, but reading the code explains it.
It works even if you add more formats later. You do not need to keep the Format object of that initial call. It will be used throughout the workbook (probably also on different sheets, but I didn't test that).
I suggest explaining what you're doing in a code comment because it's really not very obvious.
After a bit of additional research I found it explained in the PDF document OpenOffice.org's Documentation of the Microsoft Excel File Format, in chapter 4.6.2 at the bottom of page 89 (emphasis mine).
The default cell format
is always present in an Excel file, described by the XF record with the fixed index 15 (0-based). By default, it uses the
worksheet/workbook default cell style, described by the very first XF record (index 0).
So now we also know why it's xf_index => 0.
Old answer with general advice:
In general that's not supported. You need to work with formats. The docs say that there is a default format with Calibri in size 11.
The default format is Calibri 11 with all other properties off.
You need to create a format object for a specific combination of formatting, font family, font size and so on and then you can reuse that throughout the document in each cell or chart. If one of the options differs in a specific cell, you need to make an additional Format object for that.
my $default_format = $workbook->add_format( font => 'Comic Sans' );
# ...
$worksheet->write( 'A1', 'Cell A1', $default_format );
# later ...
$worksheet->write( 'Z3', 'Cell Z3', $default_format );
If you need a lot of different formatting, but always want to use the same font, it might be useful to set the font string as a format hash and use that.
my %default_formatting = ( font => 'Comic Sans' );
my $bold_format = $workbook->add_format( %default_formatting, bold => 1 );
Maybe even make a sub that helps to create the Format objects and already knows about the default font.
sub create_format {
my ($workbook, %formats) = #_;
return $workbook->add_format( font => 'Comic Sans', %formats );
}
# somewhere
my $bold_format = create_format( $workbook, bold => 1 );
$worksheet->write( 'A1', 'Cell A1', $bold_format );
That would also allow throw-away formats like this:
$worksheet->write( 'D1', 'Weird stuff', create_format(
$workbook,
bold => 1,
strikeout => 1,
shadow => 1,
bg_color => 'pink',
));

How do I parse microseconds with Time::Piece strptime?

I have a timestamp that looks like 25-OCT-10 04.11.00.000000 AM. I'm trying to convert this to a time format with
Time::Piece->strptime("25-OCT-10 04.11.00.000000 AM","%d-%b-%y %I.%M.%S.%6N %p")
but it keeps throwing errors. I've tried %OS, %SZ. They dont seem to work. Can someone tell me what I'm doing wrong?
Time::Piece doesn't support sub-second times. Try DateTime instead, for example, DateTime::Format::Strptime:
use DateTime::Format::Strptime;
my $parser = DateTime::Format::Strptime->new(
pattern => '%d-%b-%y %I.%M.%S.%6N %p',
);
my $dt = $parser->parse_datetime("25-OCT-10 04.11.00.000000 AM");
strptime won't read that. strptime works with a structure that only goes down to integer seconds, and it doesn't have any formats for recognizing non-integer numerics -- and there's no such format as N in Time::Piece's strptime. If you know that you're always expecting .000000 for a number of microseconds then you could try using ..."%I.%M.%S.000000 %p", otherwise strptime just isn't for you.
How about DateTime::Format::CLDR? A format of "dd-MMM-yy hh.mm.ss.SSSSSS a" seems to work perfectly well with that format.
use DateTime::Format::CLDR;
my $parser = DateTime::Format::CLDR->new(
pattern => "dd-MMM-yy hh.mm.ss.SSSSSS a",
locale => "en_US",
);
my $dt = $parser->parse_datetime("25-OCT-10 04.11.00.000100 AM");
say $dt->iso8601; # 2010-01-25T04:11:00
Edit: just noticed that this doesn't recognize months properly if they're all uppercase -- it recognizes "Oct" but not "OCT". A fixed version is available here and has been sent upstream for merge :)
Update: DateTime::Format::CLDR 1.11 is properly case-insensitive.

Parsedate question in Perl

In Perl, why I get different results from parsedate(2010-7-2 13:0:0) and parsedate(2010-7-2 13:00:0) ?
The 2010-7-2 13:0:0 string is not in a valid format, and is actually not being parsed at all (it appears) as evidenced by the fact that parsedate("2010-7-2") returns the same value as parsedate("2010-7-2 13:0:0") for me.
Based on the docs, it's simply parsing the YYYY-MM-DD, but not parsing the 13:0:0 at all because it is expecting it to be in HH:MM format and not HH:M format. Basically, you have to use two digits for the minutes in order for it to be valid input.
To handle your date format with more flexibility, try using DateTime::Format::Strptime
my $strp = DateTime::Format::Strptime->new(
pattern => '%Y-%m-%d %T',
locale => 'en_AU',
time_zone => 'Australia/Melbourne',
);
my $dt1 = $strp->parse_datetime('2010-7-2 13:0:0');
my $date_1 = $strp->format_datetime($dt1);
$date_1 is now converted into a well-formatted date format "2010-07-02 13:00:00". Then you can call parsedate($date_1) & get epoch.