ONO::Lib::DateTime::ToolBox

package ONO::Lib::DateTime::ToolBox;
################################################################################
# COPYRIGHT / LICENSE #
################################################################################
#
# This file is part of the ONO Software Project.
#
# Copyright (C) 2000-2025 Jos KIRPS [ www.kirps.com | jos_AT_kirps_DOT_com ]
# and The Joopita Project [ www.joopita.org | contact_AT_joopita_DOT_com ]
#
# This file, as well as other parts of the ONO Software Project or related
# elements, are FREE SOFTWARE available under the ARTISTIC LICENSE 2.0.
#
# 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.
#
# For the full license, see /ono/osr/license/LICENSE.txt, or write to
# jos_AT_kirps_DOT_com or contact_AT_joopita_DOT_com.
#
################################################################################
# END OF COPYRIGHT / LICENSE, HERE COMES THE CODE ... #
################################################################################

use strict;
use ONO::Lib::Basic;

my $ZONE;
eval "use POSIX";
if (!$@) {
$ZONE = strftime("%Z", localtime());
}

sub timestamp {

#: Get UNIX time.

return time();

}

sub get {

#: Get date and / or time, including some conversion options
#:
#: A - no absolute time in -i mode
#: b - important stuff gets bold
#: d - date format EU (dd/mm/yyyy)
#: D - date format US (mm/dd/yyyy)
#: g - important stuff gets green
#: i - intelligent mode features human readable output
#: L - remove leading zeroes (unless it's midnight)
#: m - display time if date is current date
#: M - short 3 letter month name
#: n - round up to full minute (no seconds displayed in intelligent mode)
#: N -   in intelligent mode
#: o - last online
#: r - short (dd. month)
#: R - short with optional year (dd. month yyyy if not current year)
#: s - stamp format 1 (dd/mm/yyyy \@ hh:mm)
#: S - stamp format 2 (yyyymmdd)
#: u - uppercase (first letter)
#: y - year
#: z - ?
#: Z - remove non-required zeroes

my (
$self,
$inputtime,
$timeoffset,
$options,
$lang
) = @_;

$inputtime =~ s~[^0-9]~~g;
if (length $inputtime == 8 && $inputtime =~ /^(1|2)/ && $options !~ /(r|R)/) {
$inputtime .= "000000";
}

if (!$inputtime) {
$inputtime = time;
}

# this only works with dates after 1000 AD

if ($inputtime > 10000000000000) {
$inputtime = &convert("","unix",$inputtime);
}

if (($ZONE eq "CEST" || $ZONE eq "CET") && $options =~ /z/) {
$timeoffset--;
}

my $at = "at";
my $before;
my $yesterday_at = "yesterday at";
my $today_at = "today at";
my $last_night_at = "last night at";
my $hour_ago = "hour ago";
my $hours_ago = "hours ago";
my $minutes_ago = "minutes ago";
my $seconds_ago = "seconds ago";
my $online_now = "online now";
my $last_online = "last online";
my $right_now = "right now";

if ($lang eq "de") {
$at = "um";
$before = "vor ";
$yesterday_at = "gestern um";
$today_at = "heute um";
$last_night_at = "letzte Nacht um";
$hour_ago = "Stunde";
$hours_ago = "Stunden";
$minutes_ago = "Minuten";
$seconds_ago = "Sekunden";
$online_now = "jetzt online";
$last_online = "zuletzt online";
$right_now = "gerade eben";
}

if ($lang eq "fr") {
$at = "à";
$before = "il y a ";
$yesterday_at = "hier à";
$today_at = "aujourd'hui à";
$last_night_at = "la huit passée à";
$hour_ago = "heure";
$hours_ago = "heures";
$minutes_ago = "minutes";
$seconds_ago = "secondes";
$online_now = "maintenant";
$last_online = "en ligne";
$right_now = "juste maintenant";
}

if ($lang eq "lu") {
$at = "um";
$before = "virun ";
$yesterday_at = "gëschter um";
$today_at = "haut um";
$last_night_at = "läscht Nuecht um";
$hour_ago = "Stonn";
$hours_ago = "Stonnen";
$minutes_ago = "Minutten";
$seconds_ago = "Sekonnen";
$online_now = "elo online";
$last_online = "fir d'läscht online";
$right_now = "grad elo";
}

my (
$sec,$min,$hour,
$mday,$mon,$year,
$wday,$yday,$isdst
) = localtime($inputtime+(3600*$timeoffset));
$year = $year+1900;
$mon++;

my (
$sec2,$min2,$hour2,
$mday2,$mon2,$year2,
$wday2,$yday2,$isdst2
) = localtime(time+(3600*$timeoffset));

if (!$options || $options =~ /(T|t|d|D|s|S|m)/) {

if ($mon < 10 ) {$mon = "0$mon" }
if ($mday < 10 ) {$mday = "0$mday"}
if ($hour < 10 ) {$hour = "0$hour"}
if ($min < 10 ) {$min = "0$min" }
if ($sec < 10 ) {$sec = "0$sec" }
my $timestamp = time;

if (!$options) {
return ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$timestamp,$isdst);
}

if ($options =~ /L/) {
$hour =~ s~^0~~;
$hour =~ s~^\:~0\:~;
}

if ($options =~ /t/) {
return "$hour:$min";
}

if ($options =~ /T/) {
return "$hour:$min:$sec";
}

if ($options =~ /d/) {
return &check_zeros("$mday.$mon.$year",$options);
}

if ($options =~ /D/) {
return "$mon.$mday.$year";
}

if ($options =~ /s/) {
return "$mday.$mon.$year \@ $hour:$min";
}

if ($options =~ /S/) {
return "$year$mon$mday";
}

if ($options =~ /m/) {

my $time = &check_zeros("$mday.$mon.$year",$options);

if ($mday == $mday2 && $mon == $mon2+1 && $year == $year2+1900) {
$time = "$hour:$min:$sec";
if ($options =~ /b/) {
$time = "<strong>$time</strong>";
}
if ($options =~ /g/) {
$time = qq~<span class="green">$time</span>~;
}
}

return $time;
}

}

# intelligent mode - offers human readable output

if ($options =~ /i/) {

if ($min < 10) {
$min = "0$min";
}
$year2 = $year2+1900;
$mon2++;

my $month = &month_name(0,$mon,"",$lang);
if ($options =~ /M/) {
$month = substr($month,0,3);
}
my $time = "$month $mday, $year";
if ($lang =~ /^(de|fr|lu)$/) {
$time = "$mday. $month $year";
}

if ($options =~ /A/) {

my $diff = time() - $inputtime;

if ($diff < 60) {
$time = "$before $diff $minutes_ago";
}
if ($diff < 4) {
$time = "$right_now!";
}

} else {

if ($year == $year2) {
$time = "$month $mday $at $hour:$min";
if ($lang =~ /^(de|fr|lu)$/) {
$time = "$mday. $month $at $hour:$min";
}
}

my $yesterday = $mday2-1;

if ("$year-$mon-$mday" eq "$year2-$mon2-$yesterday") {
$time = "$yesterday_at $hour:$min";
}

if ("$year-$mon-$mday" eq "$year2-$mon2-$mday2") {
$time = "$today_at $hour:$min";

if ($hour < 6) {
$time = "$last_night_at $hour:$min";
}

if ($hour2 - $hour < 5) {
$time = $before.($hour2 - $hour)." $hours_ago";
}

if ($hour2 - $hour < 2) {
$time = $before.($hour2 - $hour)." $hour_ago";
}

if ($hour2 - $hour == 1 && ($min2 - $min) < 0) {
$time = $before.(60+$min2 - $min)." $minutes_ago";
}

if ($hour2 - $hour < 1) {
$time = $before.($min2 - $min)." $minutes_ago";
if ($min2 - $min < 1 && $options !~ /(n|o)/) {
$time = $before.($sec2 - $sec)." $seconds_ago";
if ($sec2 - $sec < 10) {
$time = "$right_now!";
}
}

if ($min2 - $min < 5 && $options =~ /(n|o)/) {
$time = "$online_now!";
}
}
}
}

if ($options =~ /o/) {
$time = qq~$last_online: <span class="bold">$time</span>~;
}

if ($options =~ /u/) {
$time = ucfirst $time;
}

if ($options =~ /N/) {
$time =~ s~ ~ ~g;
}

return $time;
}

if ($options =~ /(r|R)/) {
my $day = substr($inputtime,6,2);
$day =~ s~^0~~;
if ($options =~ /R/ && substr($inputtime,0,4) != $year2+1900) {
return "$day. ".&month_name("",substr($inputtime,4,2),"u",$lang)." ".substr($inputtime,0,4);
} else {
return "$day. ".&month_name("",substr($inputtime,4,2),"u",$lang);
}
}

if ($options =~ /y/) {
return $year;
}

}

sub check_zeros {

#: Check zeroes.
#:
#: -Z remove zeroes.

my ($time,$options) = @_;

if ($options =~ /Z/) {
$time =~ s~\.0~\.~g;
$time =~ s~^0~~;
}

return $time;

}

sub num {

#: Return year, mon ot mday

my (
$sec,$min,$hour,
$mday,$mon,$year,
$wday,$yday,$isdst
) = localtime();

if ($_[1] eq "year") {
return $year+1900;
}
if ($_[1] eq "mon") {
return $mon+1;
}
if ($_[1] eq "mday") {
return $mday;
}

}

###############################################################################
# convert to "classic" or "unix"
###############################################################################

sub convert {

my (
$self,
$target,
$timestamp,
$switches,
$lang,
) = @_;

#: Convert time formats
#:
#: $lang only required for -m switch
#:
#: -3 shorter month name (3 chars)
#: -d dashes (yyyymmdd option)
#: -m month name ("date" target only)
#: -s slashes instead of periods
#: -S US only: slashes instead of periods
#: -u US time format
#: -U US time format detected on lang = en
#: -w add whitespaces
#: -Z eleminate zeros

my $return = $timestamp;

if ($switches =~ /U/ && $lang eq "en") {
$switches .= "u";
}

#: Target = "classic" : from 1285128905(10) to yyyymmddhhmmss(14)

if ($target eq "classic" && $timestamp < 19000000000000) {

my (
$sec,$min,$hour,
$mday,$mon,$year,
$wday,$yday
) = &get("",$timestamp);
$return = "$year$mon$mday$hour$min$sec";

}

#: Target = "yyyymmdd" : from 1285128905(10) to yyyymmdd or yyyy-mm-dd (-d)

if ($target eq "yyyymmdd" && $timestamp < 19000000000000) {

my (
$sec,$min,$hour,
$mday,$mon,$year,
$wday,$yday
) = &get("",$timestamp);

if ($switches =~ /d/) {
$return = "$year-$mon-$mday";
} else {
$return = "$year$mon$mday";
}

}

#: Target = "yymmdd" : from 1285128905(10) to yyyymmdd or yy-mm-dd (-d)

if ($target eq "yymmdd" && $timestamp < 19000000000000) {

my (
$sec,$min,$hour,
$mday,$mon,$year,
$wday,$yday
) = &get("",$timestamp);

$year = substr($year,2,2);

if ($switches =~ /d/) {
$return = "$year-$mon-$mday";
} else {
$return = "$year$mon$mday";
}

}

#: Target = "unix" : from yyyymmddhhmmss(14) to 1285128905(10)

if ($target eq "unix" && length $timestamp == 8) {
$timestamp = "${timestamp}000000";
}

if ($target eq "unix" && $timestamp > 19000000000000) {

eval "use Time::Local";
if (!$@) {
$return = timelocal(substr($timestamp,12,2),substr($timestamp,10,2),substr($timestamp,8,2),substr($timestamp,6,2),substr($timestamp,4,2)-1,substr($timestamp,0,4));
}

}

#: Target = "slash" : from yyyymmdd to yyyy/mm/dd

if ($target eq "slash" && length $timestamp == 8) {

return substr($timestamp,0,4)."/".substr($timestamp,4,2)."/".substr($timestamp,6,2);

}

#: Target = "date" : from yyyymmdd to dd.mm.yyyy

if ($target eq "date" && length $timestamp == 8) {

$return = substr($timestamp,6,2).".".substr($timestamp,4,2).".".substr($timestamp,0,4);
my $is_us;

if ($switches =~ /u/) {
$return = substr($timestamp,4,2).".".substr($timestamp,6,2).".".substr($timestamp,0,4);
}

if (substr($timestamp,6,2) eq "00") {
$return = substr($timestamp,4,2).".".substr($timestamp,0,4);
if (substr($timestamp,4,2) eq "00") {
$return = substr($timestamp,0,4);
}
}

if ($switches =~ /m/ && substr($timestamp,4,2) ne "00") {
my $three;
if ($switches =~ /3/) {
$three = 3;
}
my $mon = &month_name("",substr($timestamp,4,2),"u$three",$lang);
$return = substr($timestamp,6,2).". ".$mon." ".substr($timestamp,0,4);
if ($switches =~ /u/) {
$return = $mon." ".substr($timestamp,6,2).", ".substr($timestamp,0,4);
}
}

if ($switches =~ /Z/) {
$return =~ s~^0~~g;
$return =~ s~\.0~\.~g;
$return =~ s~ 0~ ~g;
}
if ($switches =~ /s/ || ($switches =~ /S/ && $switches =~ /u/)) {
$return =~ s~\.~\/~g;
}


}

#: Target = "datetime" : from yyyymmddhhmmss(14) to dd.mm.yyyy hh:mm

if ($target eq "datetime" && length $timestamp == 14) {

$return = substr($timestamp,6,2).".".substr($timestamp,4,2).".".substr($timestamp,0,4)." ".substr($timestamp,8,2).":".substr($timestamp,10,2);

}

#: Target = "datetimesecs" : from yyyymmddhhmmss(14) to dd.mm.yyyy hh:mm:ss

if ($target eq "datetimesecs" && length $timestamp == 14) {

$return = substr($timestamp,6,2).".".substr($timestamp,4,2).".".substr($timestamp,0,4)." ".substr($timestamp,8,2).":".substr($timestamp,10,2).":".substr($timestamp,12,2);

}

#: Target = "datetimereverse" : from dd/mm/yyyy \@ hh:mm:ss to yyyymmddhhmmss

if ($target eq "datetimereverse") {

$timestamp =~ s~[^0-9\ \/\@\:]~~gi;
$timestamp =~ m~(.*?)/(.*?)/(.*?) \@ (.*?):(.*?):(.*)~;

$return = "$3$2$1$4$5$6";

}

#: Target = "time" / 6 : from hhmm to hh:mm

if ($target eq "time" && length $timestamp == 6) {

$return = substr($timestamp,0,2).":".substr($timestamp,2,2).":".substr($timestamp,4,2);

}

#: Target = "time" / 4 :

if ($target eq "time" && length $timestamp == 4) {

$return = substr($timestamp,0,2).":".substr($timestamp,2,2);

}

#: Target = "time" / 3 :

if ($target eq "time" && length $timestamp == 3) {

$return = substr($timestamp,0,1).":".substr($timestamp,1,2);

}

#: Target = "daymonyear" : from yyyymmdd to dd. Month yyyy

if ($target eq "daymonyear" && length $timestamp == 8) {

my $mon_switches = "u";
if ($switches =~ /3/) {
$mon_switches .= "3";
}
my $mon = &month_name("",substr($timestamp,4,2),$mon_switches,$lang);
if ($switches =~ /u/ || $switches =~ /U/ && $lang eq "en") {
my $day = substr($timestamp,6,2);
if ($switches =~ /Z/) {
$day =~ s~^0~~;
}
$return = "$mon $day, ".substr($timestamp,0,4);
} else {
$return = substr($timestamp,6,2).". $mon ".substr($timestamp,0,4);
if ($switches =~ /Z/) {
$return =~ s~^0~~;
}
}

}

if ($switches =~ /w/) {
$return =~ s~\.~\. ~g;
$return =~ s~\/~ \/ ~g;
}

return $return;

}

sub convert_ss2hhmmss {

#: Convert seconds to hhmmss

my $secs = $_[1];

my ($mins,$hours,$whilecounter);

while ($secs > 59 && $whilecounter < 8192) {
$whilecounter++;
$mins++;
$secs = $secs-60;
}

if (!$mins) {
$mins = 0;
}
if (length $secs < 2) {
$secs = "0$secs";
}

while ($mins > 59 && $whilecounter < 8192) {
$whilecounter++;
$hours++;
$mins = $mins-60;
}

if ($hours) {
if (length $mins < 2) {
$mins = "0$mins";
}
return "$hours:$mins:$secs";
} else {
return "$mins:$secs";
}

}

sub convert_hhmmss2ss {

#: Convert hhmmss to seconds

my $time = $_[1];

if ($time =~ /^(.*?):(.*?):(.*?)$/) {
$time = $1*60*60+$2*60+$3;
}

if ($time =~ /^(.*?):(.*?)$/) {
$time = $1*60+$2;
}

return $time;

}

sub secs_in_min {
#: Return 60
return 60;
}
sub secs_in_hour {
#: Return 60*60
return 60*60;
}
sub secs_in_day {
#: Return 60*60*24
return 60*60*24;
}
sub secs_in_week {
#: Return 60*60*24*7
return 60*60*24*7;
}
sub secs_in_month {
#: Return 60*60*24*31
return 60*60*24*31;
}

###############################################################################
# max_days_per_month
###############################################################################

sub max_days_per_month {

#: Return the max number of days per month, note that Feb is 29 days here.

return (
'31','29','31',
'30','31','30',
'31','31','30',
'31','30','31'
);

}

###############################################################################
# time logic
###############################################################################

sub logic2secs {

#: Convert m/h/d string to seconds

my $secs = $_[1];

if ($secs =~ s~m~~gi) {
$secs = $secs * &secs_in_min;
}
if ($secs =~ s~h~~gi) {
$secs = $secs * &secs_in_hour;
}
if ($secs =~ s~d~~gi) {
$secs = $secs * &secs_in_day;
}

$secs =~ s~[^0-9]~~g;

return $secs;

}

###############################################################################
# month names
###############################################################################

sub month_name {

#: Return month name,
#:
#: -3 abbreviated (3 chars)
#: -a ???
#: -j ???
#: -u ???

my (
$self,
$month,
$switches,
$lang,
) = @_;

my @name = (
'',
'january',
'february',
'march',
'april',
'may',
'june',
'july',
'august',
'september',
'october',
'november',
'december',
);

if ($lang eq "de") {
@name = (
'',
'Januar',
'Februar',
'März',
'April',
'Mai',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember',
);
}

if ($lang eq "fr") {
@name = (
'',
'janvier',
'février',
'mars',
'avril',
'mai',
'juin',
'juillet',
'août',
'septembre',
'octobre',
'novembre',
'décembre',
);
}

if ($lang eq "lu") {
@name = (
'',
'Januar',
'Februar',
'März',
'Abrëll',
'Mee',
'Juni',
'Juli',
'August',
'September',
'Oktober',
'November',
'Dezember',
);
}

if ($switches =~ /j/) {
shift @name;
}
my $res = $name[$month];
if ($switches =~ /3/) {
if ($res =~ /(ä|é|û)/) {
$res = substr($res,0,4);
} else {
$res = substr($res,0,3);
}
}
if ($switches =~ /a/) {
if ($switches =~ /u/) {
for (my $i = 0; $i < 13; $i++) {
$name[$i] = ucfirst $name[$i];
if ($switches =~ /3/) {
$name[$i] = substr($name[$i],0,3);
}
}
}
return @name;
} else {
if ($switches =~ /u/) {
$res = ucfirst $res;
}
return $res;
}

}

###############################################################################
# day names
###############################################################################

sub day_name {

#: Return day name.
#:
#: -a all names
#: -m monday is zero
#: -s short (3 chars)
#: -S short (2 chars)
#: -u uppercase first

my $switches = $_[1];
if ($_[3]) {
$switches = $_[3];
}

my @name = (
'sunday',
'monday',
'tuesday',
'wednesday',
'thursday',
'friday',
'saturday',
'sunday',
);

if ($_[2] eq "de") {
@name = (
'Sonntag',
'Montag',
'Dienstag',
'Mittwoch',
'Donnerstag',
'Freitag',
'Samstag',
'Sonntag',
);
}

if ($_[2] eq "fr") {
@name = (
'Dimanche',
'Lundi',
'Mardi',
'Mercredi',
'Jeudi',
'Vendredi',
'Samedi',
'Dimanche',
);
}

if ($_[2] eq "lu") {
@name = (
'Sonndeg',
'Méindeg',
'Dënschdeg',
'Mëttwoch',
'Donneschdeg',
'Freideg',
'Samschdeg',
'Sonndeg',
);
}

if ($switches =~ /u/ && (!$_[2] || $_[2] eq "en")) {
for (my $i = 0; $i < 8; $i++) {
$name[$i] = ucfirst $name[$i];
}
}

if ($switches =~ /(s|S)/) {
my $cut = 3;
if ($switches =~ /S/) {
$cut = 2;
}
for (my $i = 0; $i < 8; $i++) {
$name[$i] = substr($name[$i],0,$cut);
}
}

if ($switches =~ /m/) {
shift @name;
}

if ($switches =~ /a/) {
return @name;
} else {
return $name[$_[1]];
}

}

###############################################################################
# string
###############################################################################

sub string {

#: Return yyyymmddhhmmss string, which may be abbreviated (default = 8 chars).

my $num = $_[1];
if ($num < 1) {
$num = 8;
}

my (
$sec,$min,$hour,
$mday,$mon,$year,
$wday,$yday,$isdst
) = localtime;
$year = $year+1900;
$mon++;
if ($mon < 10 ) {$mon = "0$mon" }
if ($mday < 10 ) {$mday = "0$mday"}
if ($hour < 10 ) {$hour = "0$hour"}
if ($min < 10 ) {$min = "0$min" }
if ($sec < 10 ) {$sec = "0$sec" }

return substr("$year$mon$mday$hour$min$sec",0,$num);

}

###############################################################################
# today
###############################################################################

sub today {

#: Return yyyymmdd.

return &string("",8);

}

sub todayhh {

#: Return yyyymmddhh.

return &string("",10);

}

sub today_std {

#: Return today.
#:
#: -d include day name
#: -m month name

my (
$sec,$min,$hour,
$mday,$mon,$year,
$wday,$yday,$isdst
) = localtime();

$year = $year+1900;

my $DATE;
if ($_[1] =~ /d/) {
$DATE .= ucfirst &day_name("",$wday,"","su").", ";
}
if ($_[1] =~ /m/) {
$DATE .= &month_name("",$mon+1,"3u")." ";
} else {
$DATE .= ($mon+1)."/";
}
$DATE .= "$mday, $year";

return $DATE;

}

###############################################################################
# yesterday
###############################################################################

sub yesterday {

my ($self,$year,$mon,$mday) = @_;

#: Return yesterday's date.
#: See also tomorrow().

if (length $year == 8) {
$mday = substr($year,6,2);
$mon = substr($year,4,2);
$year = substr($year,0,4);
}

$mday--;
if ($mday < 1) {
$mon--;
$mday = 31;
}
if ($mday > 30 && ($mon == 4 || $mon == 6 || $mon == 9 || $mon == 11)) {
$mday = 30;
}
if ($mday > 28 && $mon == 2) {
$mday = 28;
}

if ($mon < 1) {
$year--;
$mon = 12;
$mday = 31;
}

($mon,$mday) = &add_leading_zero($mon,$mday);

return "$year$mon$mday";

}

sub add_leading_zero {

#: Add leading zero to mon and mday.

my ($mon,$mday) = @_;

if (length $mon < 2) {
$mon = "0$mon";
}
if (length $mday < 2) {
$mday = "0$mday";
}

return ($mon,$mday);

}

###############################################################################
# tomorrow
###############################################################################

sub tomorrow {

#: Return tomorrow's date.
#: See also yesterday().

my ($self,$year,$mon,$mday) = @_;

$mday++;
if ($mday > 30 || ($mday > 29 && ($mon == 4 || $mon == 6 || $mon == 9 || $mon == 11)) || ($mday > 27 && $mon == 2)) {
$mon++;
$mday = 1;
}

if ($mon > 12) {
$year++;
$mon = 1;
}

($mon,$mday) = &add_leading_zero($mon,$mday);

return "$year$mon$mday";

}

###############################################################################
# nextdays
###############################################################################

sub nextdays {

#: This returns the exact date of an upcoming next day (example: what's the date of next friday?).

my (
$self,
$year,$mon,$mday,
$weekday,
$target,
) = @_;

my $days_to_go = $target - $weekday;

# if weekdays match then we'll jump to the next week...

if ($days_to_go < 1) {
$days_to_go = 6;
}

$mday = $mday + $days_to_go;

my $days_in_month = 31;
if ($mon == 4 || $mon == 6 || $mon == 9 || $mon == 11) {
$days_in_month = 30;
}
if ($mon == 2) {
$days_in_month = 28;
}

if ($mday > $days_in_month) {
$mon++;
$mday = $mday - $days_in_month;
}

if ($mon > 12) {
$year++;
$mon = 1;
}

($mon,$mday) = &add_leading_zero($mon,$mday);

return "$year$mon$mday";

}

###############################################################################
# select date
###############################################################################

sub select_date_save {

#: Old version, used until Oct 2019, doesn't support date picker, better use select_date_saver() instead.

my (
$self,
$yyyy,
$mm,
$dd,
) = @_;

# turn any input into YYYYMMDD format

$yyyy = ONO::Lib::Basic->add_leading_zeroes($yyyy,4);
$mm = ONO::Lib::Basic->add_leading_zeroes($mm,2);
$dd = ONO::Lib::Basic->add_leading_zeroes($dd,2);

my $date = "$yyyy$mm$dd";
$date =~ s~[^0-9]~~g;

return $date;

}

sub select_date_saver {

# Save date, new version, also supports date picker.

my (
$self,
$name,
$vars_ref,
) = @_;

my %vars = %$vars_ref;

# classic select menu

$vars{"${name}_year"} = ONO::Lib::Basic->add_leading_zeroes($vars{"${name}_year"},4);
$vars{"${name}_month"} = ONO::Lib::Basic->add_leading_zeroes($vars{"${name}_month"},2);
$vars{"${name}_day"} = ONO::Lib::Basic->add_leading_zeroes($vars{"${name}_day"},2);

my $date = qq~$vars{"${name}_year"}$vars{"${name}_month"}$vars{"${name}_day"}~;

# use date picker, if selected

if (length $vars{"${name}_picker"} == 10 && $vars{"${name}_picker"} =~ /^(.*?)(\/|-)(.*?)(\/|-)(.*?)$/) {
if ($vars{"${name}_picker_format"} eq "ddmmyyyy") {
$date = "$5$3$1";
}
if ($vars{"${name}_picker_format"} eq "mmddyyyy") {
$date = "$5$1$3";
}
}

$date =~ s~[^0-9]~~g;

if (length $date != 8) {
$date = "";
}

return $date;

}

sub select_date {

my (
$self,
$name,
$from,
$to,
$default,
$lang,
$flags,
) = @_;

#: Date selector.
#:
#: $from and $to format: YYYY
#: $default format: YYYYMMDD
#:
#: -C compact (zero padding)
#: -d disabled
#: -E force use EU format
#: -j no jquery loading (prevent double loading, required for plupload compatibility for example)
#: -J no javascript loading (only if -P is used)
#: -m use month names
#: -n null is allowed
#: -P picker, make sure to use select_date_saver then
#: -r reverse years
#: -t today
#: -T add table code
#: -U force use US format

my ($NULL,$TODAY,$DIS,$PAD);

if ($flags =~ /n/) {
$NULL = qq~<option value=""></option><option value="">--</option>~;
}
if ($flags =~ /t/) {
$TODAY = qq~<option value="">today</option><option value="">--</option>~;
}
if ($flags =~ /d/) {
$DIS = " disabled";
}
if ($flags =~ /C/) {
$PAD = qq~ class="p0"~
}

my $YYYY = substr($default,0,4);
my $MM = substr($default,4,2);
my $DD = substr($default,6,2);

my ($DAY,$MON,$YEAR);

for (my $i = 1; $i < 200; $i++) {

my $j = $i;
if ($i < 10) {
$j = "0$i";
}

if ($i < 32) {
my $sel;
if ($DD == $i) {
$sel = " selected";
}
$DAY .= qq~<option value="$j"$sel>$i</option>~;
}
if ($i < 13) {
my $sel;
if ($MM == $i) {
$sel = " selected";
}
my $m = $i;
if ($flags =~ /m/) {
$m = &month_name(0,$i,"3u",$lang);
}
$MON .= qq~<option value="$j"$sel>$m</option>~;
}
my $y = $from-1+$i;
if ($y < $to+1) {
my $sel;
if ($YYYY == $y) {
$sel = " selected";
}
if ($flags =~ /r/) {
$YEAR = qq~<option value="$y"$sel>$y</option>$YEAR~;
} else {
$YEAR .= qq~<option value="$y"$sel>$y</option>~;
}
}

}

my $DATE = qq~<td$PAD><div class="select"><select name="${name}_day"$DIS>$NULL$DAY</select></div></td>
<td>/</td>
<td$PAD><div class="select"><select name="${name}_month"$DIS>$NULL$MON</select></div></td>
<td>/</td>
<td$PAD><div class="select"><select name="${name}_year"$DIS>$TODAY$NULL$YEAR</select></div></td>
~;

if (($lang eq "en" || $flags =~ /U/) && $flags !~ /E/) {
$DATE = qq~<td$PAD><div class="select"><select name="${name}_month"$DIS>$NULL$MON</select></div></td>
<td>/</td>
<td$PAD><div class="select"><select name="${name}_day"$DIS>$NULL$DAY</select></div></td>
<td>/</td>
<td$PAD><div class="select"><select name="${name}_year"$DIS>$TODAY$NULL$YEAR</select></div></td>
~;

# this doesn't make sense ???
# if ($flags =~ /m/) {
# $DATE = qq~<td$PAD><div class="select"><select name="${name}_month"$DIS>$NULL$MON</select></div></td>
# <td></td>
# <td$PAD><div class="select"><select name="${name}_day"$DIS>$NULL$DAY</select></div></td>
# <td>,</td>
# <td$PAD><div class="select"><select name="${name}_year"$DIS>$TODAY$NULL$YEAR</select></div></td>
# ~;
# }
}

if ($flags =~ /P/) {

my @FORMAT = ("$DD/$MM/$YYYY","dd/mm/yyyy","ddmmyyyy");
if (($lang eq "en" || $flags =~ /U/) && $flags !~ /E/) {
@FORMAT = ("$MM-$DD-$YYYY","mm-dd-yyyy","mmddyyyy");
}
if (!$MM || !$DD || !$YYYY) {
$FORMAT[0] = "";
}

my $datelang = "en";
if ($lang =~ /^(da|de|es|fi|fr|nl|pt)$/) {
$datelang = $lang;
}
if ($lang =~ /^(lu|lb)$/) {
$datelang = "de";
}

my $JS;
if ($flags !~ /J/) {
my $TODAY = &todayhh();
if ($flags !~ /j/) {
$JS .= qq~<script src="/ono/osr/javascript/jquery/v2/jquery.js?$TODAY"></script>~;
}
$JS .= qq~<link href="/ono/osr/javascript/datepicker/dist/css/datepicker.min.css?$TODAY" rel="stylesheet" type="text/css">
<script src="/ono/osr/javascript/datepicker/dist/js/datepicker.js?$TODAY"></script>
<script src="/ono/osr/javascript/datepicker/dist/js/i18n/datepicker.$datelang.js?$TODAY"></script>
~;
}

$DATE = qq~<td>
<a href="javascript:void(0);" onclick="onojs_tablecell('ono_cal_${name}_picker');onojs_hide('ono_cal_${name}_selector');
onojs_setvalue('ono_cal_${name}_picker_field','$FORMAT[0]');onojs_focus('ono_cal_${name}_picker_field');">
<img class="block24" src="/ono/osr/images/icons/crystal/32x32/apps/cal.png" alt="">
</a>
</td>
<td id="ono_cal_${name}_picker" class="p0 hide">
$JS
<input id="ono_cal_${name}_picker_field" type="text" name="${name}_picker" value="" data-language="$datelang" data-date-format="$FORMAT[1]" class="datepicker-here">
<input type="hidden" name="${name}_picker_format" value="$FORMAT[2]">
</td>
<td id="ono_cal_${name}_selector" class="p0"><table class="default_table">$DATE</table></td>
~;

}

if ($flags =~ /T/) {
$DATE = qq~<table class="default_table">$DATE</table>~;
}

return $DATE;

}

###############################################################################
# secs to hh:mm:ss
###############################################################################

sub secs_hhmmss {

#: Convert secs to hhmmss.

my $input = $_[1];

my $hour = 0;
my $min = 0;
my $sec = $input;

my $whilecounter;

while ($sec > 59 && $whilecounter < 1024) {
$whilecounter++;
$min++;
$sec = $sec - 60;
}
while ($min > 59 && $whilecounter < 1024) {
$whilecounter++;
$hour++;
$min = $min - 60;
if ($min < 10) {
$min = "0$min";
}
if (!$min) {
$min = "00";
}
}
if ($sec < 10) {
$sec = "0$sec";
}
if (!$sec) {
$sec = "00";
}
if ($hour) {
return "$hour:$min:$sec";
} else {
return "$min:$sec";
}

}

###############################################################################
# birthday
###############################################################################

sub birthday_select {

#: Select birthday.

my (
$self,
$name,
$birthday,
$year,
$lang,
$BLK_ref,
) = @_;

my ($date_y,$date_m,$date_d) = &birthday("",$birthday,$year,$BLK_ref);

my $BIRTHDAY = qq~<td><div class="select"><select name="${name}_m">$date_m</select></div></td>
<td><div class="select"><select name="${name}_d">$date_d</select></div></td>
<td><div class="select"><select name="${name}_y">$date_y</select></div></td>
~;

if ($lang =~ /^(de|fr|lu)$/) {
$BIRTHDAY = qq~<td><div class="select"><select name="${name}_d">$date_d</select></div></td>
<td><div class="select"><select name="${name}_m">$date_m</select></div></td>
<td><div class="select"><select name="${name}_y">$date_y</select></div></td>
~;
}

return $BIRTHDAY;

}

sub birthday {

#: Select birthday, subroutine, see birthday_select().

my (
$self,
$birthday,
$year,
$BLK_ref,
) = @_;

my %BLK = %$BLK_ref,

my ($date_y,$date_m,$date_d);

for (my $i = $year-4; $i > 1899; $i--) {

my $sel;
if ($i == substr($birthday,0,4) || (substr($birthday,0,4) < 1900 && $i == $year-18)) {
$sel = "selected";
}

$date_y .= qq~<option $sel value="$i">$i</option>~;
}

for (my $i = 1; $i < 13; $i++) {
my $sel;
if ($i == substr($birthday,4,2)) {
$sel = "selected";
}
my $j = $i;
if ($i < 10) {
$j = "0$i";
}
my $mn = ucfirst &month_name("",$i);
$date_m .= qq~<option $sel value="$j">$BLK{$mn}</option>~;
}

for (my $i = 1; $i < 32; $i++) {
my $sel;
if ($i == substr($birthday,6,2)) {
$sel = "selected";
}
my $j = $i;
if ($i < 10) {
$j = "0$i";
}
$date_d .= qq~<option $sel value="$j">$i</option>~;
}

return ($date_y,$date_m,$date_d);

}

###############################################################################
# schoolyear
###############################################################################

sub schoolyear {

#: Return schoolyear.

my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = &get();

my ($year1,$year2) = ($year-1,$year);
if ($mon > 7) {
($year1,$year2) = ($year,$year+1);
}

return "${year1}_${year2}";

}

###############################################################################
# mmm to mm
###############################################################################

sub mmm2mm {

#: Convert month (3-char) to number (2-char).

my $mmm = lc $_[1];

$mmm =~ s~jan~01~;
$mmm =~ s~feb~02~;
$mmm =~ s~mar~03~;
$mmm =~ s~apr~04~;
$mmm =~ s~may~05~;
$mmm =~ s~jun~06~;
$mmm =~ s~jul~07~;
$mmm =~ s~aug~08~;
$mmm =~ s~sep~09~;
$mmm =~ s~oct~10~;
$mmm =~ s~nov~11~;
$mmm =~ s~dec~12~;

return $mmm;

}

###############################################################################
# end of script
###############################################################################

1;

__END__