Projekt

Allgemein

Profil

Herunterladen (17,5 KB) Statistiken
| Zweig: | Markierung: | Revision:
541272c5 Moritz Bunkus
#====================================================================
# LX-Office ERP
# Copyright (C) 2004
# Based on SQL-Ledger Version 2.1.9
# Web http://www.lx-office.org
#
#=====================================================================
# SQL-Ledger Accounting
# Copyright (C) 1998-2002
#
# Author: Dieter Simader
# Email: dsimader@sql-ledger.org
# Web: http://www.sql-ledger.org
#
# Contributors: Thomas Bayen <bayen@gmx.de>
# Antti Kaihola <akaihola@siba.fi>
# Moritz Bunkus (tex code)
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#======================================================================
#
# Translations and number/date formatting
#
#======================================================================

package Locale;

2508bbb1 Moritz Bunkus
use DateTime;
cc042e07 Sven Schöling
use Encode;
5367525b Moritz Bunkus
use List::Util qw(first);
cc042e07 Sven Schöling
use List::MoreUtils qw(any);
faef45c2 Moritz Bunkus
541272c5 Moritz Bunkus
use SL::LXDebug;
faef45c2 Moritz Bunkus
use SL::Common;
cc042e07 Sven Schöling
use SL::Iconv;
dc3cd296 Moritz Bunkus
use SL::Inifile;
541272c5 Moritz Bunkus
76c486e3 Sven Schöling
use strict;

bed8ba6d Moritz Bunkus
my %locales_by_country;

541272c5 Moritz Bunkus
sub new {
$main::lxdebug->enter_sub();

1682d89d Moritz Bunkus
my ($type, $country) = @_;

be6f6cfd Moritz Bunkus
$country ||= $::lx_office_conf{system}->{language};
1682d89d Moritz Bunkus
$country =~ s|.*/||;
$country =~ s|\.||g;
dc3cd296 Moritz Bunkus
bed8ba6d Moritz Bunkus
if (!$locales_by_country{$country}) {
my $self = {};
bless $self, $type;

$self->_init($country);
541272c5 Moritz Bunkus
bed8ba6d Moritz Bunkus
$locales_by_country{$country} = $self;
}
dc3cd296 Moritz Bunkus
$main::lxdebug->leave_sub();

bed8ba6d Moritz Bunkus
return $locales_by_country{$country}
dc3cd296 Moritz Bunkus
}

sub _init {
my $self = shift;
my $country = shift;

dffb7fd7 Moritz Bunkus
$self->{countrycode} = $country;

541272c5 Moritz Bunkus
if ($country && -d "locale/$country") {
local *IN;
1682d89d Moritz Bunkus
if (open(IN, "<", "locale/$country/all")) {
541272c5 Moritz Bunkus
my $code = join("", <IN>);
eval($code);
close(IN);
}
dffb7fd7 Moritz Bunkus
}
faef45c2 Moritz Bunkus
dbda14c2 Moritz Bunkus
binmode STDOUT, ":utf8";
binmode STDERR, ":utf8";
cc042e07 Sven Schöling
dbda14c2 Moritz Bunkus
$self->{iconv} = SL::Iconv->new('UTF-8', 'UTF-8');
$self->{iconv_reverse} = SL::Iconv->new('UTF-8', 'UTF-8');
$self->{iconv_english} = SL::Iconv->new('ASCII', 'UTF-8');
$self->{iconv_iso8859} = SL::Iconv->new('ISO-8859-15', 'UTF-8');
$self->{iconv_to_iso8859} = SL::Iconv->new('UTF-8', 'ISO-8859-15');
$self->{iconv_utf8} = SL::Iconv->new('UTF-8', 'UTF-8');
541272c5 Moritz Bunkus
dffb7fd7 Moritz Bunkus
$self->_read_special_chars_file($country);
541272c5 Moritz Bunkus
push @{ $self->{LONG_MONTH} },
("January", "February", "March", "April",
"May ", "June", "July", "August",
"September", "October", "November", "December");
push @{ $self->{SHORT_MONTH} },
(qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec));
dc3cd296 Moritz Bunkus
}
541272c5 Moritz Bunkus
dc3cd296 Moritz Bunkus
sub _handle_markup {
03ea9764 Moritz Bunkus
my $self = shift;
my $str = shift;
541272c5 Moritz Bunkus
03ea9764 Moritz Bunkus
my $escaped = 0;
my $new_str = '';

for (my $i = 0; $i < length $str; $i++) {
my $char = substr $str, $i, 1;

if ($escaped) {
if ($char eq 'n') {
$new_str .= "\n";

} elsif ($char eq 'r') {
$new_str .= "\r";

} elsif ($char eq 's') {
$new_str .= ' ';

} elsif ($char eq 'x') {
$new_str .= chr(hex(substr($str, $i + 1, 2)));
$i += 2;
dc3cd296 Moritz Bunkus
03ea9764 Moritz Bunkus
} else {
$new_str .= $char;
}

$escaped = 0;

} elsif ($char eq '\\') {
$escaped = 1;

} else {
$new_str .= $char;
}
}
dc3cd296 Moritz Bunkus
03ea9764 Moritz Bunkus
return $new_str;
dc3cd296 Moritz Bunkus
}

sub _read_special_chars_file {
my $self = shift;
my $country = shift;

if (! -f "locale/$country/special_chars") {
$self->{special_chars_map} = {};
return;
}

$self->{special_chars_map} = Inifile->new("locale/$country/special_chars", 'verbatim' => 1);

foreach my $format (keys %{ $self->{special_chars_map} }) {
next if (($format eq 'FILE') || ($format eq 'ORDER') || (ref $self->{special_chars_map}->{$format} ne 'HASH'));

if ($format ne lc $format) {
$self->{special_chars_map}->{lc $format} = $self->{special_chars_map}->{$format};
delete $self->{special_chars_map}->{$format};
$format = lc $format;
}

my $scmap = $self->{special_chars_map}->{$format};
abbd99fd Moritz Bunkus
my $order = $self->{iconv}->convert($scmap->{order});
dc3cd296 Moritz Bunkus
delete $scmap->{order};

foreach my $key (keys %{ $scmap }) {
abbd99fd Moritz Bunkus
$scmap->{$key} = $self->_handle_markup($self->{iconv}->convert($scmap->{$key}));
dc3cd296 Moritz Bunkus
abbd99fd Moritz Bunkus
my $new_key = $self->_handle_markup($self->{iconv}->convert($key));
dc3cd296 Moritz Bunkus
if ($key ne $new_key) {
$scmap->{$new_key} = $scmap->{$key};
delete $scmap->{$key};
}
}

$self->{special_chars_map}->{"${format}-reverse"} = { reverse %{ $scmap } };

$scmap->{order} = [ map { $self->_handle_markup($_) } split m/\s+/, $order ];
$self->{special_chars_map}->{"${format}-reverse"}->{order} = [ grep { $_ } map { $scmap->{$_} } reverse @{ $scmap->{order} } ];
}
541272c5 Moritz Bunkus
}

sub text {
c836425c Moritz Bunkus
my $self = shift;
my $text = shift;
541272c5 Moritz Bunkus
87b0de4c Moritz Bunkus
return $text->translated if (ref($text) || '') eq 'SL::Locale::String';

5923380f Sven Schöling
if ($self->{texts}->{$text}) {
c836425c Moritz Bunkus
$text = $self->{iconv}->convert($self->{texts}->{$text});
} else {
$text = $self->{iconv_english}->convert($text);
faef45c2 Moritz Bunkus
}

c836425c Moritz Bunkus
if (@_) {
$text = Form->format_string($text, @_);
}

return $text;
541272c5 Moritz Bunkus
}

00f9b4aa Wulf Coulmann
sub lang_to_locale {
my ($self, $requested_lang) = @_;

my $requested_locale;
$requested_locale = 'de' if $requested_lang =~ m/^_(de|deu|ger)/i;
3f2c80ca Sven Schöling
$requested_locale = 'en' if $requested_lang =~ m/^_(en|uk|us|gr)/i;
$requested_locale = 'fr' if $requested_lang =~ m/^_fr/i;
00f9b4aa Wulf Coulmann
$requested_locale ||= 'de';

return $requested_locale;
}

541272c5 Moritz Bunkus
sub findsub {
$main::lxdebug->enter_sub();

my ($self, $text) = @_;
5367525b Moritz Bunkus
my $text_rev = lc $self->{iconv_reverse}->convert($text);
642d15dd Moritz Bunkus
$text_rev =~ s/[\s\-]+/_/g;
541272c5 Moritz Bunkus
5367525b Moritz Bunkus
if (!$self->{texts_reverse}) {
$self->{texts_reverse} = { };
while (my ($original, $translation) = each %{ $self->{texts} }) {
$original = lc $original;
$original =~ s/[^a-z0-9]/_/g;
$original =~ s/_+/_/g;
1682d89d Moritz Bunkus
5367525b Moritz Bunkus
$translation = lc $translation;
7d897015 Moritz Bunkus
$translation =~ s/[\s\-]+/_/g;
1682d89d Moritz Bunkus
5367525b Moritz Bunkus
$self->{texts_reverse}->{$translation} ||= [ ];
push @{ $self->{texts_reverse}->{$translation} }, $original;
}
}
1682d89d Moritz Bunkus
f5f077a7 Moritz Bunkus
my $sub_name;
$sub_name = first { defined(&{ "::${_}" }) } @{ $self->{texts_reverse}->{$text_rev} } if $self->{texts_reverse}->{$text_rev};
$sub_name ||= $text_rev if ($text_rev =~ m/^[a-z][a-z0-9_]+$/) && defined &{ "::${text_rev}" };
1682d89d Moritz Bunkus
5367525b Moritz Bunkus
$main::form->error("$text not defined in locale/$self->{countrycode}/all") if !$sub_name;
541272c5 Moritz Bunkus
$main::lxdebug->leave_sub();

5367525b Moritz Bunkus
return $sub_name;
541272c5 Moritz Bunkus
}

sub date {
$main::lxdebug->enter_sub();

my ($self, $myconfig, $date, $longformat) = @_;

abde405d Moritz Bunkus
if (!$date) {
$main::lxdebug->leave_sub();
return '';
}

541272c5 Moritz Bunkus
my $longdate = "";
my $longmonth = ($longformat) ? 'LONG_MONTH' : 'SHORT_MONTH';

76c486e3 Sven Schöling
my ($spc, $yy, $mm, $dd);

541272c5 Moritz Bunkus
# get separator
abde405d Moritz Bunkus
$spc = $myconfig->{dateformat};
$spc =~ s/\w//g;
$spc = substr($spc, 1, 1);
541272c5 Moritz Bunkus
abde405d Moritz Bunkus
if ($date =~ /\D/) {
if ($myconfig->{dateformat} =~ /^yy/) {
($yy, $mm, $dd) = split /\D/, $date;
}
if ($myconfig->{dateformat} =~ /^mm/) {
($mm, $dd, $yy) = split /\D/, $date;
541272c5 Moritz Bunkus
}
if ($myconfig->{dateformat} =~ /^dd/) {
abde405d Moritz Bunkus
($dd, $mm, $yy) = split /\D/, $date;
541272c5 Moritz Bunkus
}
abde405d Moritz Bunkus
} else {
$date = substr($date, 2);
($yy, $mm, $dd) = ($date =~ /(..)(..)(..)/);
}
541272c5 Moritz Bunkus
abde405d Moritz Bunkus
$dd *= 1;
$mm--;
$yy = ($yy < 70) ? $yy + 2000 : $yy;
$yy = ($yy >= 70 && $yy <= 99) ? $yy + 1900 : $yy;

if ($myconfig->{dateformat} =~ /^dd/) {
if (defined $longformat && $longformat == 0) {
$mm++;
$dd = "0$dd" if ($dd < 10);
$mm = "0$mm" if ($mm < 10);
$longdate = "$dd$spc$mm$spc$yy";
} else {
$longdate = "$dd";
$longdate .= ($spc eq '.') ? ". " : " ";
$longdate .= &text($self, $self->{$longmonth}[$mm]) . " $yy";
}
} elsif ($myconfig->{dateformat} eq "yyyy-mm-dd") {

# Use German syntax with the ISO date style "yyyy-mm-dd" because
008c2e15 Moritz Bunkus
# kivitendo is mainly used in Germany or German speaking countries.
abde405d Moritz Bunkus
if (defined $longformat && $longformat == 0) {
$mm++;
$dd = "0$dd" if ($dd < 10);
$mm = "0$mm" if ($mm < 10);
$longdate = "$yy-$mm-$dd";
} else {
$longdate = "$dd. ";
$longdate .= &text($self, $self->{$longmonth}[$mm]) . " $yy";
}
} else {
if (defined $longformat && $longformat == 0) {
$mm++;
$dd = "0$dd" if ($dd < 10);
$mm = "0$mm" if ($mm < 10);
$longdate = "$mm$spc$dd$spc$yy";
} else {
$longdate = &text($self, $self->{$longmonth}[$mm]) . " $dd, $yy";
}
541272c5 Moritz Bunkus
}

$main::lxdebug->leave_sub();

return $longdate;
}

sub parse_date {
6815c0cc Sven Schöling
$main::lxdebug->enter_sub(2);
541272c5 Moritz Bunkus
my ($self, $myconfig, $date, $longformat) = @_;
76c486e3 Sven Schöling
my ($spc, $yy, $mm, $dd);
541272c5 Moritz Bunkus
unless ($date) {
6815c0cc Sven Schöling
$main::lxdebug->leave_sub(2);
541272c5 Moritz Bunkus
return ();
}

# get separator
$spc = $myconfig->{dateformat};
$spc =~ s/\w//g;
$spc = substr($spc, 1, 1);

if ($date =~ /\D/) {
if ($myconfig->{dateformat} =~ /^yy/) {
($yy, $mm, $dd) = split /\D/, $date;
} elsif ($myconfig->{dateformat} =~ /^mm/) {
($mm, $dd, $yy) = split /\D/, $date;
} elsif ($myconfig->{dateformat} =~ /^dd/) {
($dd, $mm, $yy) = split /\D/, $date;
}
} else {
$date = substr($date, 2);
($yy, $mm, $dd) = ($date =~ /(..)(..)(..)/);
}

2daee349 Sven Schöling
$_ ||= 0 for ($dd, $mm, $yy);
$_ *= 1 for ($dd, $mm, $yy);
541272c5 Moritz Bunkus
$yy = ($yy < 70) ? $yy + 2000 : $yy;
$yy = ($yy >= 70 && $yy <= 99) ? $yy + 1900 : $yy;

6815c0cc Sven Schöling
$main::lxdebug->leave_sub(2);
541272c5 Moritz Bunkus
return ($yy, $mm, $dd);
}

2508bbb1 Moritz Bunkus
sub parse_date_to_object {
646cb2aa Moritz Bunkus
my ($self, $string, %params) = @_;
2508bbb1 Moritz Bunkus
646cb2aa Moritz Bunkus
$params{dateformat} ||= $::myconfig{dateformat} || 'yy-mm-dd';
$params{numberformat} ||= $::myconfig{numberformat} || '1,000.00';
my $num_separator = $params{numberformat} =~ m{,\d+$} ? ',' : '.';

my ($date_str, $time_str) = split m{\s+}, $string, 2;
my ($yy, $mm, $dd) = $self->parse_date(\%params, $date_str);

2daee349 Sven Schöling
my ($hour, $minute, $second) = split m/:/, ($time_str || '');
$second ||= '0';

($second, my $millisecond) = split quotemeta($num_separator), $second, 2;
$_ ||= 0 for ($hour, $minute, $millisecond);

646cb2aa Moritz Bunkus
$millisecond = substr $millisecond, 0, 3;
$millisecond .= '0' x (3 - length $millisecond);

return undef unless $yy && $mm && $dd;
return DateTime->new(year => $yy, month => $mm, day => $dd, hour => $hour * 1, minute => $minute * 1, second => $second * 1, nanosecond => $millisecond * 1000000);
2508bbb1 Moritz Bunkus
}

3711d95e Thomas Heck
sub format_date_object_to_time {
my ($self, $datetime, %params) = @_;

2fcb9a09 Thomas Heck
my $format = $::myconfig{timeformat} || 'hh:mm';
$format =~ s/hh/\%H/;
$format =~ s/mm/\%M/;
$format =~ s/ss/\%S/;

return $datetime->strftime($format);
3711d95e Thomas Heck
}

38e08b2f Moritz Bunkus
sub format_date_object {
my ($self, $datetime, %params) = @_;

d16e003e Moritz Bunkus
my $format = $params{dateformat} || $::myconfig{dateformat} || 'yyyy-mm-dd';
my $num_separator = ($params{numberformat} || $::myconfig{numberformat} || '1,000.00') =~ m{,\d+$} ? ',' : '.';
aa8fc8ca Moritz Bunkus
$format =~ s/yy(?:yy)?/\%Y/;
38e08b2f Moritz Bunkus
$format =~ s/mm/\%m/;
$format =~ s/dd/\%d/;

my $precision = $params{precision} || 'day';
$precision =~ s/s$//;
my %precision_spec_map = (
d16e003e Moritz Bunkus
millisecond => '%H:%M:%S' . $num_separator . '%3N',
second => '%H:%M:%S',
minute => '%H:%M',
hour => '%H',
38e08b2f Moritz Bunkus
);

$format .= ' ' . $precision_spec_map{$precision} if $precision_spec_map{$precision};

return $datetime->strftime($format);
}

541272c5 Moritz Bunkus
sub reformat_date {
6815c0cc Sven Schöling
$main::lxdebug->enter_sub(2);
541272c5 Moritz Bunkus
my ($self, $myconfig, $date, $output_format, $longformat) = @_;

6815c0cc Sven Schöling
$main::lxdebug->leave_sub(2) and return "" unless ($date);
541272c5 Moritz Bunkus
my ($yy, $mm, $dd) = $self->parse_date($myconfig, $date);

$output_format =~ /d+/;
substr($output_format, $-[0], $+[0] - $-[0]) =
sprintf("%0" . (length($&)) . "d", $dd);

$output_format =~ /m+/;
substr($output_format, $-[0], $+[0] - $-[0]) =
sprintf("%0" . (length($&)) . "d", $mm);

$output_format =~ /y+/;
a7d906be Moritz Bunkus
substr($output_format, $-[0], $+[0] - $-[0]) = $yy;
541272c5 Moritz Bunkus
6815c0cc Sven Schöling
$main::lxdebug->leave_sub(2);
541272c5 Moritz Bunkus
return $output_format;
}

b32553a3 Moritz Bunkus
sub format_date {
$main::lxdebug->enter_sub();

8484285f Moritz Bunkus
my $self = shift;
my $myconfig = shift;
my $yy = shift;
my $mm = shift;
my $dd = shift;
my $yy_len = shift || 4;
b32553a3 Moritz Bunkus
e500d8b2 Moritz Bunkus
($yy, $mm, $dd) = ($yy->year, $yy->month, $yy->day) if ref $yy eq 'DateTime';

b32553a3 Moritz Bunkus
$main::lxdebug->leave_sub() and return "" unless $yy && $mm && $dd;

8484285f Moritz Bunkus
$yy = $yy % 100 if 2 == $yy_len;

my $format = ref $myconfig eq '' ? "$myconfig" : $myconfig->{dateformat};
b32553a3 Moritz Bunkus
$format =~ s{ d+ }{ sprintf("%0" . (length($&)) . "d", $dd) }gex;
$format =~ s{ m+ }{ sprintf("%0" . (length($&)) . "d", $mm) }gex;
8484285f Moritz Bunkus
$format =~ s{ y+ }{ sprintf("%0${yy_len}d", $yy) }gex;
b32553a3 Moritz Bunkus
$main::lxdebug->leave_sub();

return $format;
}

dc3cd296 Moritz Bunkus
sub quote_special_chars {
my $self = shift;
my $format = lc shift;
my $string = shift;

if ($self->{special_chars_map} && $self->{special_chars_map}->{$format} && $self->{special_chars_map}->{$format}->{order}) {
my $scmap = $self->{special_chars_map}->{$format};

map { $string =~ s/\Q${_}\E/$scmap->{$_}/g } @{ $scmap->{order} };
}

return $string;
}

sub unquote_special_chars {
my $self = shift;
my $format = shift;

return $self->quote_special_chars("${format}-reverse", shift);
}

sub remap_special_chars {
my $self = shift;
my $src_format = shift;
my $dst_format = shift;

return $self->quote_special_chars($dst_format, $self->quote_special_chars("${src_format}-reverse", shift));
}

f5bc2335 Moritz Bunkus
sub raw_io_active {
my $self = shift;

return !!$self->{raw_io_active};
}

cc042e07 Sven Schöling
sub with_raw_io {
my $self = shift;
my $fh = shift;
my $code = shift;

f5bc2335 Moritz Bunkus
$self->{raw_io_active} = 1;
cc042e07 Sven Schöling
binmode $fh, ":raw";
$code->();
dbda14c2 Moritz Bunkus
binmode $fh, ":utf8";
f5bc2335 Moritz Bunkus
$self->{raw_io_active} = 0;
cc042e07 Sven Schöling
}

a873249c Moritz Bunkus
sub set_numberformat_wo_thousands_separator {
my $self = shift;
my $myconfig = shift || \%::myconfig;

$self->{saved_numberformat} = $myconfig->{numberformat};
$myconfig->{numberformat} =~ s/^1[,\.]/1/;
}

sub restore_numberformat {
my $self = shift;
my $myconfig = shift || \%::myconfig;

$myconfig->{numberformat} = $self->{saved_numberformat} if $self->{saved_numberformat};
}

51c64daf Moritz Bunkus
sub get_local_time_zone {
my $self = shift;
$self->{local_time_zone} ||= DateTime::TimeZone->new(name => 'local');
return $self->{local_time_zone};
}

1e846de1 Moritz Bunkus
sub language_join {
my ($self, $items, %params) = @_;

$items ||= [];
$params{conjunction} ||= $::locale->text('and');
my $num = scalar @{ $items };

return 0 == $num ? ''
: 1 == $num ? $items->[0]
: join(', ', @{ $items }[0..$num - 2]) . ' ' . $params{conjunction} . ' ' . $items->[$num - 1];
}

541272c5 Moritz Bunkus
1;
1e846de1 Moritz Bunkus
38e08b2f Moritz Bunkus
__END__

=pod

=encoding utf8

=head1 NAME

d7946c4c Sven Schöling
Locale - Functions for dealing with locale-dependent information
38e08b2f Moritz Bunkus
=head1 SYNOPSIS

use Locale;
use DateTime;

my $locale = Locale->new('de');
my $now = DateTime->now_local;
print "Current date and time: ", $::locale->format_date_object($now, precision => 'second'), "\n";

=head1 OVERVIEW

TODO: write overview

=head1 FUNCTIONS

=over 4

=item C<date>

TODO: Describe date

=item C<findsub>

TODO: Describe findsub

=item C<format_date>

TODO: Describe format_date

=item C<format_date_object $datetime, %params>

Formats the C<$datetime> object accoring to the user's locale setting.

The parameter C<precision> can control whether or not the time
component is formatted as well:

=over 4

=item * C<day>

Only format the year, month and day. This is also the default.

=item * C<hour>

Add the hour to the date.

=item * C<minute>

Add hour:minute to the date.

=item * C<second>

Add hour:minute:second to the date.

d16e003e Moritz Bunkus
=item * C<millisecond>

Add hour:minute:second.millisecond to the date. The decimal separator
is derived from the number format.

=item * C<numberformat>

The number format to use, e.g. C<1,000.00>. If unset the user's
current number format is used.

=item * C<dateformat>

The date format to use, e.g. C<mm/dd/yy>. If unset the user's current
date format is used.

38e08b2f Moritz Bunkus
=back

=item C<get_local_time_zone>

TODO: Describe get_local_time_zone

=item C<lang_to_locale>

TODO: Describe lang_to_locale

=item C<new>

TODO: Describe new

=item C<parse_date>

TODO: Describe parse_date

646cb2aa Moritz Bunkus
=item C<parse_date_to_object $string, %params>

Parses a date and optional timestamp in C<$string> and returns an
instance of L<DateTime>. The date and number formats used are the ones
the user has currently selected. They can be overriden by passing them
in as parameters to this function, though.
38e08b2f Moritz Bunkus
646cb2aa Moritz Bunkus
The time stamps can have up to millisecond precision.
38e08b2f Moritz Bunkus
=item C<quote_special_chars>

TODO: Describe quote_special_chars

=item C<raw_io_active>

TODO: Describe raw_io_active

=item C<reformat_date>

TODO: Describe reformat_date

=item C<remap_special_chars>

TODO: Describe remap_special_chars

=item C<restore_numberformat>

TODO: Describe restore_numberformat

=item C<set_numberformat_wo_thousands_separator>

TODO: Describe set_numberformat_wo_thousands_separator

=item C<text>

TODO: Describe text

=item C<unquote_special_chars>

TODO: Describe unquote_special_chars

=item C<with_raw_io>

TODO: Describe with_raw_io

=back

=head1 BUGS

Nothing here yet.

=head1 AUTHOR

Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>

=cut