aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Smedegaard <dr@jones.dk>2024-08-31 12:24:59 +0200
committerJonas Smedegaard <dr@jones.dk>2024-09-01 09:18:49 +0200
commit0f9cda3eb8afd89a9a2470d9a4c19a5c1c69b092 (patch)
treeb1526d09e39a1d12fbd7b25bd0483f458a51fe05
parent30e1a34d6f69f514c6c1c908c0f77a651074a175 (diff)
streamline locale and timezone handling
-rwxr-xr-xbin/events2md.pl65
-rw-r--r--lib/Object/Groupware.pm37
-rw-r--r--lib/Object/Groupware/Calendar.pm12
-rw-r--r--lib/Object/Groupware/DAV.pm14
-rw-r--r--lib/Object/Groupware/Event.pm57
5 files changed, 155 insertions, 30 deletions
diff --git a/bin/events2md.pl b/bin/events2md.pl
index e9ef842..7192cb0 100755
--- a/bin/events2md.pl
+++ b/bin/events2md.pl
@@ -15,6 +15,9 @@ use URI;
use DateTime;
use Path::Tiny;
use Text::Xslate;
+use POSIX qw(locale_h); # resolve LC_TIME
+use locale;
+use DateTime::TimeZone;
use Object::Groupware::DAV;
use Object::Groupware::Calendar;
@@ -24,7 +27,9 @@ if ( IO::Interactive::Tiny::is_interactive() ) {
}
# set defaults and parse command-line options
-my ( $BASE_URI, $CALENDAR_URI, $SKELDIR, $OUTPUT_FILE );
+my ($BASE_URI, $CALENDAR_URI, $SKELDIR, $OUTPUT_FILE, $CALENDAR_LANG,
+ $CALENDAR_TIME_ZONE, %GROUPWARE_OPTIONS
+);
$BASE_URI = $ENV{CAL_DAV_URL_BASE};
$CALENDAR_URI = $ENV{CAL_DAV_URL_CALENDAR};
$SKELDIR = $ENV{SKELDIR} || "$Bin/../templates";
@@ -34,6 +39,53 @@ $CALENDAR_URI ||= shift @ARGV
if @ARGV;
$OUTPUT_FILE = shift @ARGV
if @ARGV;
+$CALENDAR_LANG = $ENV{CAL_LANG} || setlocale(LC_TIME);
+$CALENDAR_TIME_ZONE
+ = DateTime::TimeZone->new( name => ( $ENV{CAL_TIME_ZONE} || 'local' ), );
+
+# extend DateTime locale with form LONGER
+# * omit year and second
+# * unabbreviate weekday and month
+# * interpose time preposition in combined date and time, where known
+my %at = (
+ C => " 'at' ",
+ ar => " 'في' ",
+ da => " 'kl.' ",
+ de => " 'um' ",
+ en => " 'at' ",
+ es => " 'a las' ",
+ fr => " 'à' ",
+ he => " 'בשעה' ",
+ it => " 'alle' ",
+ ja => "'に'",
+ no => " 'kl.' ",
+ ru => " 'в' ",
+ zh => "'在'",
+);
+my $dt_locale = DateTime::Locale->load($CALENDAR_LANG);
+my ( $locale, $lang ) = $dt_locale->code =~ /^((\w+)(?:-\w+)?)/;
+my $dt = DateTime->now( locale => $dt_locale );
+my %dt_locale_data = $dt_locale->locale_data;
+$dt_locale_data{code} = "${locale}-LONGER";
+$dt_locale_data{name} .= ' nouns unabbreviated';
+$dt_locale_data{date_format_medium} = $dt->locale->format_for('MMMMEd');
+$dt_locale_data{date_format_medium} ||= $dt->locale->format_for('MMMEd');
+$dt_locale_data{date_format_medium} =~ s/\bMMM\b/MMMM/;
+$dt_locale_data{date_format_medium} =~ s/\bMMM\b/MMMM/;
+$dt_locale_data{date_format_medium} =~ s/\bE\b/EEEE/;
+$dt_locale_data{time_format_medium} = $dt->locale->format_for('Hm');
+$dt_locale_data{datetime_format_medium}
+ =~ s/^\{1\}\K,? (?=\{0\}$)/$at{$lang}/
+ if $at{$lang};
+%GROUPWARE_OPTIONS = (
+ dt_locale => DateTime::Locale::FromData->new( \%dt_locale_data ),
+ dt_time_zone => $CALENDAR_TIME_ZONE,
+);
+$log->infof(
+ 'Will use locale %s and time zone %s',
+ $GROUPWARE_OPTIONS{dt_locale}->code,
+ $GROUPWARE_OPTIONS{dt_time_zone}->name,
+);
# resolve calendar URIs
my ( $base_uri, $calendar_uri, $calendar );
@@ -56,6 +108,7 @@ if ( $base_uri->scheme eq 'http' or $base_uri->scheme eq 'https' ) {
user => $ENV{CAL_DAV_USER},
pass => $ENV{CAL_DAV_PASS},
uri => $base_uri,
+ %GROUPWARE_OPTIONS,
);
$calendar = $session->get($calendar_uri);
}
@@ -68,12 +121,18 @@ elsif ( $base_uri->scheme eq 'file' ) {
$log->debug('parse local calendar data...');
my $path = path( $base_uri->file );
if ( $path->is_file ) {
- $calendar = Object::Groupware::Calendar->new( filename => "$path" );
+ $calendar = Object::Groupware::Calendar->new(
+ filename => "$path",
+ %GROUPWARE_OPTIONS,
+ );
}
else {
my $data;
$path->visit( sub { $data .= $_->slurp_raw if $_->is_file } );
- $calendar = Object::Groupware::Calendar->new( data => $data );
+ $calendar = Object::Groupware::Calendar->new(
+ data => $data,
+ %GROUPWARE_OPTIONS,
+ );
}
}
if ( $log->is_trace ) {
diff --git a/lib/Object/Groupware.pm b/lib/Object/Groupware.pm
new file mode 100644
index 0000000..083f415
--- /dev/null
+++ b/lib/Object/Groupware.pm
@@ -0,0 +1,37 @@
+use v5.36;
+use Feature::Compat::Class 0.07;
+
+package Object::Groupware 0.01;
+
+class Object::Groupware;
+
+use utf8;
+
+use Log::Any qw( );
+
+field $log = Log::Any->get_logger;
+
+field $dt_locale : param : reader = undef;
+field $dt_time_zone : param : reader = undef;
+field $dt_span_time_prefix : param : reader = '';
+
+ADJUST {
+ if ( defined $self->dt_locale ) {
+ $dt_locale = DateTime::Locale->load( $self->dt_locale )
+ unless $self->dt_locale isa DateTime::Locale::FromData;
+ $log->debugf(
+ 'Class %s set up to use locale %s (%s)',
+ __CLASS__, $self->dt_locale->code, $self->dt_locale->name
+ );
+ }
+ if ( defined $self->dt_time_zone ) {
+ $dt_time_zone = DateTime::TimeZone->new( name => $self->dt_time_zone )
+ unless $self->dt_time_zone isa DateTime::TimeZone;
+ $log->debugf(
+ 'Class %s set up to use time zone %s',
+ __CLASS__, $self->dt_time_zone->name
+ );
+ }
+}
+
+1;
diff --git a/lib/Object/Groupware/Calendar.pm b/lib/Object/Groupware/Calendar.pm
index 7226782..386a749 100644
--- a/lib/Object/Groupware/Calendar.pm
+++ b/lib/Object/Groupware/Calendar.pm
@@ -3,7 +3,7 @@ use Feature::Compat::Class 0.07;
package Object::Groupware::Calendar 0.01;
-class Object::Groupware::Calendar;
+class Object::Groupware::Calendar : isa(Object::Groupware);
use utf8;
@@ -39,7 +39,15 @@ method events ( $set = undef, $period = undef )
$set->start, $set->end
) if $set;
- my @events = map { Object::Groupware::Event->new( entry => $_ ) } sort {
+ my $dt_locale = $self->dt_locale;
+ my $dt_time_zone = $self->dt_time_zone;
+ my @events = map {
+ Object::Groupware::Event->new(
+ entry => $_,
+ dt_locale => $dt_locale,
+ dt_time_zone => $dt_time_zone,
+ )
+ } sort {
$a->start->compare( $b->start )
|| $a->start->compare( $b->start )
|| $a->summary cmp $b->summary
diff --git a/lib/Object/Groupware/DAV.pm b/lib/Object/Groupware/DAV.pm
index 5c191e3..5dc789d 100644
--- a/lib/Object/Groupware/DAV.pm
+++ b/lib/Object/Groupware/DAV.pm
@@ -3,13 +3,12 @@ use Feature::Compat::Class 0.07;
package Object::Groupware::DAV 0.01;
-class Object::Groupware::DAV;
+class Object::Groupware::DAV : isa(Object::Groupware);
use utf8;
use Feature::Compat::Try;
-use POSIX qw(locale_h); # resolve LC_TIME
-use locale;
+
use Net::Netrc;
use IO::Interactive::Tiny;
use Log::Any qw( );
@@ -20,9 +19,6 @@ use DateTime;
use Object::Groupware::Calendar;
-# use system locale to format DateTime objects parsed from iCal data
-DateTime->DefaultLocale( setlocale(LC_TIME) );
-
field $log = Log::Any->get_logger;
field $uri : param;
@@ -80,7 +76,11 @@ method get ($new_uri)
# 3. PROPFIND with depth: 1
# as documented at <https://stackoverflow.com/a/11673483>
- return Object::Groupware::Calendar->new( data => $session->cal );
+ return Object::Groupware::Calendar->new(
+ data => $session->cal,
+ dt_locale => $self->dt_locale,
+ dt_time_zone => $self->dt_time_zone,
+ );
}
1;
diff --git a/lib/Object/Groupware/Event.pm b/lib/Object/Groupware/Event.pm
index 52fa143..c5ccb6e 100644
--- a/lib/Object/Groupware/Event.pm
+++ b/lib/Object/Groupware/Event.pm
@@ -3,50 +3,71 @@ use Feature::Compat::Class 0.07;
package Object::Groupware::Event 0.01;
-class Object::Groupware::Event;
+class Object::Groupware::Event : isa(Object::Groupware);
use utf8;
use Log::Any qw( );
use Feature::Compat::Try;
+use DateTime::Locale;
field $log = Log::Any->get_logger;
field $entry : param;
+field $dt_locale;
-field $begin : reader = $entry->start;
-field $date_begin : reader = $begin->strftime('%A %e. %B');
-field $time_begin : reader = $begin->strftime('%k.%M');
-field $end : reader = $entry->end;
-field $date_end : reader;
-field $time_end : reader;
+field $begin : reader = $entry->start;
+field $end : reader = $entry->end;
field $datespan : reader;
field $timespan : reader;
field $time_brief : reader;
field $summary : reader = $entry->summary;
-field $description : reader = $entry->description;
+field $description : reader = $entry->description || '';
field $location : reader = $entry->_simple_property('location');
field $price : reader;
field @attendees;
field @attachments;
ADJUST {
- if ( defined $end ) {
- $date_end = $end->strftime('%A %e. %B');
- $time_end = $end->strftime('%k.%M');
+ if ( $self->dt_locale ) {
+ $dt_locale = $self->dt_locale;
+ $begin->set_locale($dt_locale);
+ $end->set_locale($dt_locale) if defined $end;
+ }
+ else {
+
+ # we need the object regardless, to fetch CLDR patterns
+ $dt_locale = DateTime::Locale->load('en_US');
+ }
+
+ $begin->set_locale( $self->dt_locale );
+
+ for ( $self->dt_time_zone || () ) {
+ $begin->set_time_zone($_);
+ $end->set_time_zone($_) if defined $end;
}
$datespan
- = ( defined $end and $date_end ne $date_begin )
- ? ucfirst("$date_begin - $date_end")
- : ucfirst("$date_begin");
- $timespan
- = ( defined $end and not $entry->all_day )
- ? ucfirst("$date_begin kl. $time_begin-$time_end")
+ = ( defined $end
+ and $end->clone->truncate( to => 'day' ) ne
+ $begin->clone->truncate( to => 'day' ) )
+ ? ucfirst(
+ sprintf '%s - %s',
+ $begin->format_cldr( $dt_locale->date_format_medium() ),
+ $end->format_cldr( $dt_locale->date_format_medium() )
+ )
+ : ucfirst( $begin->format_cldr( $dt_locale->date_format_medium() ) );
+ $timespan = ( defined $end and not $entry->all_day )
+ ? ucfirst(
+ sprintf '%s-%s',
+ $begin->format_cldr( $dt_locale->datetime_format_medium() ),
+ $end->format_cldr( $dt_locale->time_format_medium() )
+ )
: undef;
$time_brief
= $entry->all_day
? $datespan
- : ucfirst("$date_begin kl. $time_begin");
+ : ucfirst(
+ $begin->format_cldr( $dt_locale->datetime_format_medium() ) );
$description =~ s/\n\n[Pp]ris:\s*((?!\n).+)\s*\z//m;
$price = $1;