The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Mojolicious::Plugin::Vparam::Datetime;
use Mojo::Base -strict;
use Mojolicious::Plugin::Vparam::Common;

sub check_date($) {
    return 'Value is not defined'       unless defined $_[0];
    return 'Value is not set'           unless length  $_[0];
    return 0;
}

sub check_time($) {
    return 'Value is not defined'       unless defined $_[0];
    return 'Value is not set'           unless length  $_[0];
    return 0;
}

sub check_datetime($) {
    return 'Value is not defined'       unless defined $_[0];
    return 'Value is not set'           unless length  $_[0];
    return 0;
}

# Get a string and return DateTime or undef.
# Have a hack for parse Russian data and time.
sub parse_date($;$) {
    my ($str, $tz) = @_;

    return undef unless defined $str;
    s{^\s+}{}, s{\s+$}{} for $str;
    return undef unless length $str;

    my $dt;

    if( $str =~ m{^\d+$} ) {
        $dt = DateTime->from_epoch( epoch => int $str, time_zone => 'local' );
    } elsif( $str =~ m{^[+-]} ) {
        my @relative = $str =~ m{
            ^([+-])             # sign
            \s*
            (?:(\d+)\s+)?       # days
            (?:(\d+):)??        # hours
            (\d+)               # minutes
            (?::(\d+))?         # seconds
        $}x;
        $dt = DateTime->now(time_zone => 'local');
        my $sub = $relative[0] eq '+' ? 'add' : 'subtract';
        $dt->$sub(days      => int $relative[1])    if defined $relative[1];
        $dt->$sub(hours     => int $relative[2])    if defined $relative[2];
        $dt->$sub(minutes   => int $relative[3])    if defined $relative[3];
        $dt->$sub(seconds   => int $relative[4])    if defined $relative[4];
    } else {
        # RU format
        if( $str =~ s{^(\d{1,2})\.(\d{1,2})\.(\d{1,4})(.*)$}{$3-$2-$1$4} ) {
            my $cur_year = DateTime->now(time_zone => 'local')->strftime('%Y');
            my $cur_len  = length( $cur_year ) - 1;
            # Less digit year
            if( my ($year) = $str =~ m{^(\d{1,$cur_len})-} ) {
                $str = substr($cur_year, 0, 4 - length($year)) . $str;
            }
        }
        # If looks like time add it
        $str = DateTime->now(time_zone => 'local')->strftime('%F ') . $str
            if $str =~ m{^\d{2}:};

        $dt = eval { DateTime::Format::DateParse->parse_datetime( $str ); };
        return undef if $@;
    }

    return undef unless $dt;

    # Always local timezone
    $tz //= DateTime->now(time_zone => 'local')->strftime('%z');
    $dt->set_time_zone( $tz );

    return $dt;
}

sub register {
    my ($class, $self, $app, $conf) = @_;

    $app->vtype(
        date        =>
            load    => ['DateTime', 'DateTime::Format::DateParse'],
            pre     => sub { parse_date trim  $_[1] },
            valid   => sub { check_date       $_[1] },
            post    => sub {
                return unless defined $_[1];
                return ref($_[1]) && ( $conf->{date} || ! $_[2]->{blessed} )
                    ? $_[1]->strftime( $conf->{date} )
                    : $_[1];
            },
    );

    $app->vtype(
        time        =>
            load    => ['DateTime', 'DateTime::Format::DateParse'],
            pre     => sub { parse_date trim  $_[1] },
            valid   => sub { check_time       $_[1] },
            post    => sub {
                return unless defined $_[1];
                return ref($_[1]) && ( $conf->{time} || ! $_[2]->{blessed} )
                    ? $_[1]->strftime( $conf->{time} )
                    : $_[1];
            },
    );

    $app->vtype(
        datetime    =>
            load    => ['DateTime', 'DateTime::Format::DateParse'],
            pre     => sub { parse_date trim  $_[1] },
            valid   => sub { check_datetime   $_[1] },
            post    => sub {
                return unless defined $_[1];
                return ref($_[1]) && ( $conf->{datetime} || ! $_[2]->{blessed} )
                    ? $_[1]->strftime( $conf->{datetime} )
                    : $_[1];
            },
    );


    return;
}

1;