| 158 | | package WWW::CybozuOffice6::Calendar::Event; |
| 159 | | |
| 160 | | sub new { |
| 161 | | my $class = shift; |
| 162 | | my $self = { |
| 163 | | is_full_day => 0, |
| 164 | | modified => DateTime->now, |
| 165 | | }; |
| 166 | | bless $self, $class; |
| 167 | | return unless $self->parse(@_); |
| 168 | | $self; |
| 169 | | } |
| 170 | | |
| 171 | | sub id { shift->_accessor( 'id', @_ ) } |
| 172 | | sub start { shift->_accessor( 'start', @_ ) } |
| 173 | | sub end { shift->_accessor( 'end', @_ ) } |
| 174 | | sub summary { shift->_accessor( 'summary', @_ ) } |
| 175 | | sub description { shift->_accessor( 'description', @_ ) } |
| 176 | | sub created { shift->_accessor( 'created', @_ ) } |
| 177 | | sub modified { shift->_accessor( 'modified', @_ ) } |
| 178 | | sub is_full_day { shift->_accessor( 'is_full_day', @_ ) } |
| 179 | | sub comment { shift->_accessor( 'comment', @_ ) } |
| 180 | | |
| 181 | | sub _accessor { |
| 182 | | my $this = shift; |
| 183 | | my $key = shift; |
| 184 | | $this->{$key} = shift if @_; |
| 185 | | $this->{$key}; |
| 186 | | } |
| 187 | | |
| 188 | | sub parse { |
| 189 | | my ( $this, %param ) = @_; |
| 190 | | |
| 191 | | $this->{id} = $param{id} || '0'; |
| 192 | | $this->{time_zone} = $param{time_zone} || 'Asia/Tokyo'; |
| 193 | | |
| 194 | | my $start = $this->to_datetime( $param{start_date}, $param{start_time} ); |
| 195 | | my $end = $this->to_datetime( $param{end_date}, $param{end_time} ); |
| 196 | | return unless $start && $end; |
| 197 | | |
| 198 | | # (start_time == empty) => A full-day event |
| 199 | | # (start_time != empty) && (end_time == empty) => A malformed event |
| 200 | | if ( $param{start_time} eq ':' ) { |
| 201 | | $start = $start->truncate( to => 'day' ); |
| 202 | | $end = $end->add( days => 1 )->truncate( to => 'day' ); |
| 203 | | $this->{is_full_day} = 1; |
| 204 | | } |
| 205 | | elsif ( $param{end_time} eq ':' ) { |
| 206 | | $end = $start->clone->add( minutes => 10 ); |
| 207 | | } |
| 208 | | $this->{start} = $start; |
| 209 | | $this->{end} = $end; |
| 210 | | |
| 211 | | $this->{created} = DateTime->from_epoch( epoch => $param{created} || 0 ); |
| 212 | | |
| 213 | | my $summary = |
| 214 | | ( $param{abbrev} ? $param{abbrev} . ': ' : '' ) . $param{summary}; |
| 215 | | $this->{summary} = $summary; |
| 216 | | $this->{description} = $param{description} || $summary; |
| 217 | | 1; |
| 218 | | } |
| 219 | | |
| 220 | | # convert (ymd, hms) pair to a DateTime object (timezone: localtime) |
| 221 | | sub to_datetime { |
| 222 | | my $this = shift; |
| 223 | | my ( $ymd, $hms ) = @_; |
| 224 | | |
| 225 | | my %args; |
| 226 | | return |
| 227 | | unless $ymd |
| 228 | | && ( $ymd =~ m!^(\d+)/(\d+)/(\d+)$! |
| 229 | | || $ymd =~ m!^da\.(\d+)\.(\d+)\.(\d+)$! ); |
| 230 | | @args{qw(year month day)} = ( $1, $2, $3 ); |
| 231 | | |
| 232 | | if ( $hms && $hms ne ':' ) { |
| 233 | | return unless $hms =~ m!^(\d+):(\d+)(?:\:?(\d+)?)$!; |
| 234 | | @args{qw(hour minute second)} = ( $1, $2, $3 || 0 ); |
| 235 | | @args{qw(hour minute second)} = ( 23, 59, 59 ) if $args{hour} > 23; |
| 236 | | } |
| 237 | | else { |
| 238 | | @args{qw(hour minute second)} = ( 0, 0, 0 ); |
| 239 | | } |
| 240 | | |
| 241 | | $args{time_zone} = $this->{time_zone}; |
| 242 | | |
| 243 | | DateTime->new(%args); |
| 244 | | } |
| 245 | | |
| 246 | | package WWW::CybozuOffice6::Calendar::RecurrentEvent; |
| 247 | | |
| 248 | | use base qw( WWW::CybozuOffice6::Calendar::Event ); |
| 249 | | |
| 250 | | sub rrule { shift->_accessor( 'rrule', @_ ) } |
| 251 | | |
| 252 | | # for compatibility |
| 253 | | sub frequency { shift->_accessor( 'frequency', @_ ) } |
| 254 | | sub frequency_value { shift->_accessor( 'frequency_value', @_ ) } |
| 255 | | sub until { shift->_accessor( 'until', @_ ) } |
| 256 | | |
| 257 | | sub exdates { |
| 258 | | my $this = shift; |
| 259 | | return unless $this->{exdates}; |
| 260 | | my $exdates = $this->{exdates}; |
| 261 | | wantarray ? @$exdates : @$exdates[0]; |
| 262 | | } |
| 263 | | |
| 264 | | our %FREQUENCY = ( |
| 265 | | y => 'YEARLY', |
| 266 | | m => 'MONTHLY', |
| 267 | | w => 'WEEKLY', |
| 268 | | d => 'DAILY', |
| 269 | | n => 'WEEKDAYS' |
| 270 | | ); |
| 271 | | |
| 272 | | sub parse { |
| 273 | | my ( $this, %param ) = @_; |
| 274 | | $this->SUPER::parse(%param); |
| 275 | | |
| 276 | | # frequency |
| 277 | | my $freq = $param{freq}; |
| 278 | | return unless $freq && exists $FREQUENCY{$freq}; |
| 279 | | |
| 280 | | # rrule |
| 281 | | my %rrule = (); |
| 282 | | if ( $FREQUENCY{$freq} eq 'WEEKDAYS' ) { |
| 283 | | %rrule = ( FREQ => 'WEEKLY', BYDAY => 'MO,TU,WE,TH,FR' ); |
| 284 | | } |
| 285 | | else { |
| 286 | | %rrule = ( FREQ => $FREQUENCY{$freq} ); |
| 287 | | } |
| 288 | | if ( $param{freq_value} =~ /^\d(SU|MO|TU|WE|TH|FR|SA)$/ ) { |
| 289 | | $rrule{BYDAY} = $param{freq_value}; |
| 290 | | $rrule{INTERVAL} = 1; |
| 291 | | } |
| 292 | | |
| 293 | | # until |
| 294 | | if ( $param{until_date} =~ m!^(\d+)/(\d+)/(\d+)$! |
| 295 | | || $param{until_date} =~ m!^da\.(\d+)\.(\d+)\.(\d+)$! ) |
| 296 | | { |
| 297 | | my %args = ( year => $1, month => $2, day => $3 ); |
| 298 | | my $until; |
| 299 | | if ( $this->{is_full_day} ) { |
| 300 | | $until = $this->to_datetime( $param{until_date}, ':' ); |
| 301 | | } |
| 302 | | else { |
| 303 | | $until = $this->{end}->clone->set(%args); |
| 304 | | $until->set_time_zone('UTC'); # timezone must be UTC |
| 305 | | } |
| 306 | | $rrule{UNTIL} = $until; |
| 307 | | } |
| 308 | | |
| 309 | | $this->{rrule} = \%rrule; |
| 310 | | |
| 311 | | # exdates |
| 312 | | if ( defined $param{exdates} ) { |
| 313 | | my @exdates; |
| 314 | | for ( @{ $param{exdates} } ) { |
| 315 | | push @exdates, $this->to_datetime( $_, $param{start_time} ); |
| 316 | | } |
| 317 | | $this->{exdates} = \@exdates; |
| 318 | | } |
| 319 | | |
| 320 | | # for compatibility |
| 321 | | $this->{frequency} = $FREQUENCY{$freq}; |
| 322 | | $this->{frequency_value} = $param{freq_value} || 0; |
| 323 | | $this->{until} = $rrule{UNTIL} if exists $rrule{UNTIL}; |
| 324 | | |
| 325 | | 1; |
| 326 | | } |
| 327 | | |