* Copyright (C) 2005-2011 Regis Houssin * Copyright (C) 2011-2015 Juanjo Menent * Copyright (C) 2017 Ferran Marcet * Copyright (C) 2018-2024 Charlene Benke * Copyright (C) 2024 MDW * Copyright (C) 2024 Frédéric France * * 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 3 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, see . * or see https://www.gnu.org/ */ /** * \file htdocs/core/lib/date.lib.php * \brief Set of function to manipulate dates */ /** * Return an array with timezone values * * @return array Array with timezone values */ function get_tz_array() { $tzarray = array( -11 => "Pacific/Midway", -10 => "Pacific/Fakaofo", -9 => "America/Anchorage", -8 => "America/Los_Angeles", -7 => "America/Dawson_Creek", -6 => "America/Chicago", -5 => "America/Bogota", -4 => "America/Anguilla", -3 => "America/Araguaina", -2 => "America/Noronha", -1 => "Atlantic/Azores", 0 => "Africa/Abidjan", 1 => "Europe/Paris", 2 => "Europe/Helsinki", 3 => "Europe/Moscow", 4 => "Asia/Dubai", 5 => "Asia/Karachi", 6 => "Indian/Chagos", 7 => "Asia/Jakarta", 8 => "Asia/Hong_Kong", 9 => "Asia/Tokyo", 10 => "Australia/Sydney", 11 => "Pacific/Noumea", 12 => "Pacific/Auckland", 13 => "Pacific/Enderbury" ); return $tzarray; } /** * Return server timezone string * * @return string PHP server timezone string ('Europe/Paris') */ function getServerTimeZoneString() { return @date_default_timezone_get(); } /** * Return server timezone int. * * @param string $refgmtdate Reference period for timezone (timezone differs on winter and summer. May be 'now', 'winter' or 'summer') * @return float An offset in hour (+1 for Europe/Paris on winter and +2 for Europe/Paris on summer). Note some countries use half and even quarter hours. */ function getServerTimeZoneInt($refgmtdate = 'now') { if (method_exists('DateTimeZone', 'getOffset')) { // Method 1 (include daylight) $gmtnow = dol_now('gmt'); $yearref = dol_print_date($gmtnow, '%Y'); $monthref = dol_print_date($gmtnow, '%m'); $dayref = dol_print_date($gmtnow, '%d'); if ($refgmtdate == 'now') { $newrefgmtdate = $yearref.'-'.$monthref.'-'.$dayref; } elseif ($refgmtdate == 'summer') { $newrefgmtdate = $yearref.'-08-01'; } else { $newrefgmtdate = $yearref.'-01-01'; } $newrefgmtdate .= 'T00:00:00+00:00'; $localtz = new DateTimeZone(getServerTimeZoneString()); $localdt = new DateTime($newrefgmtdate, $localtz); $tmp = -1 * $localtz->getOffset($localdt); //print $refgmtdate.'='.$tmp; } else { $tmp = 0; dol_print_error(null, 'PHP version must be 5.3+'); } $tz = round(($tmp < 0 ? 1 : -1) * abs($tmp / 3600)); return $tz; } /** * Add a delay to a date * * @param int $time Date timestamp (or string with format YYYY-MM-DD) * @param int $duration_value Value of delay to add * @param string $duration_unit Unit of added delay (d, m, y, w, h, i) * @param int $ruleforendofmonth Change the behavior of PHP over data-interval, 0 or 1 * @return int New timestamp * @see convertSecondToTime(), convertTimeToSeconds() */ function dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth = 0) { if (empty($duration_value)) { return $time; } if ($duration_unit == 's') { return $time + ($duration_value); } if ($duration_unit == 'i') { return $time + (60 * $duration_value); } if ($duration_unit == 'h') { return $time + (3600 * $duration_value); } if ($duration_unit == 'w') { return $time + (3600 * 24 * 7 * $duration_value); } $deltastring = 'P'; if ($duration_value > 0) { $deltastring .= abs($duration_value); $sub = false; } if ($duration_value < 0) { $deltastring .= abs($duration_value); $sub = true; } if ($duration_unit == 'd') { $deltastring .= "D"; } if ($duration_unit == 'm') { $deltastring .= "M"; } if ($duration_unit == 'y') { $deltastring .= "Y"; } $date = new DateTime(); if (getDolGlobalString('MAIN_DATE_IN_MEMORY_ARE_GMT')) { $date->setTimezone(new DateTimeZone('UTC')); } $date->setTimestamp($time); $interval = new DateInterval($deltastring); if ($sub) { $date->sub($interval); } else { $date->add($interval); } //Change the behavior of PHP over data-interval when the result of this function is Feb 29 (non-leap years), 30 or Feb 31 (so php returns March 1, 2 or 3 respectively) if ($ruleforendofmonth == 1 && $duration_unit == 'm') { $timeyear = (int) dol_print_date($time, '%Y'); $timemonth = (int) dol_print_date($time, '%m'); $timetotalmonths = (($timeyear * 12) + $timemonth); $monthsexpected = ($timetotalmonths + $duration_value); $newtime = $date->getTimestamp(); $newtimeyear = (int) dol_print_date($newtime, '%Y'); $newtimemonth = (int) dol_print_date($newtime, '%m'); $newtimetotalmonths = (($newtimeyear * 12) + $newtimemonth); if ($monthsexpected < $newtimetotalmonths) { $newtimehours = (int) dol_print_date($newtime, '%H'); $newtimemins = (int) dol_print_date($newtime, '%M'); $newtimesecs = (int) dol_print_date($newtime, '%S'); $datelim = dol_mktime($newtimehours, $newtimemins, $newtimesecs, $newtimemonth, 1, $newtimeyear); $datelim -= (3600 * 24); $date->setTimestamp($datelim); } } return $date->getTimestamp(); } /** * Convert hours and minutes into seconds * * @param int $iHours Hours * @param int $iMinutes Minutes * @param int $iSeconds Seconds * @return int Time into seconds * @see convertSecondToTime() */ function convertTime2Seconds($iHours = 0, $iMinutes = 0, $iSeconds = 0) { $iResult = ((int) $iHours * 3600) + ((int) $iMinutes * 60) + (int) $iSeconds; return $iResult; } /** Return, in clear text, value of a number of seconds in days, hours and minutes. * Can be used to show a duration. * * @param int $iSecond Number of seconds * @param string $format Output format * - 'all': total delay days hour:min like "2 days 12:30", * - 'allwithouthour': total delay days without hour part like "2 days", * - 'allhourmin': total delay with format hours:min like "60:30", * - 'allhourminsec': total delay with format hours:min:sec like "60:30:10", * - 'allhour': total delay hours without min/sec like "60:30", * - 'fullhour': total delay hour decimal like "60.5" for 60:30, * - 'hour': only hours part "12", * - 'min': only minutes part "30", * - 'sec': only seconds part, * - 'month': only month part, * - 'year': only year part); * @param int $lengthOfDay Length of day (default 86400 seconds for 1 day, 28800 for 8 hour) * @param int $lengthOfWeek Length of week (default 7) * @return string Formatted text of duration * Example: 0 return 00:00, 3600 return 1:00, 86400 return 1d, 90000 return 1 Day 01:00 * @see convertTime2Seconds() */ function convertSecondToTime($iSecond, $format = 'all', $lengthOfDay = 86400, $lengthOfWeek = 7) { global $langs; if (empty($lengthOfDay)) { $lengthOfDay = 86400; // 1 day = 24 hours } if (empty($lengthOfWeek)) { $lengthOfWeek = 7; // 1 week = 7 days } $nbHbyDay = $lengthOfDay / 3600; if ($format == 'all' || $format == 'allwithouthour' || $format == 'allhour' || $format == 'allhourmin' || $format == 'allhourminsec') { if ((int) $iSecond === 0) { return '0'; // This is to avoid having 0 return a 12:00 AM for en_US } $sTime = ''; $sDay = 0; $sWeek = 0; if ($iSecond >= $lengthOfDay) { for ($i = $iSecond; $i >= $lengthOfDay; $i -= $lengthOfDay) { $sDay++; $iSecond -= $lengthOfDay; } $dayTranslate = $langs->trans("Day"); if ($iSecond >= ($lengthOfDay * 2)) { $dayTranslate = $langs->trans("Days"); } } if ($lengthOfWeek < 7) { if ($sDay) { if ($sDay >= $lengthOfWeek) { $sWeek = (int) (($sDay - $sDay % $lengthOfWeek) / $lengthOfWeek); $sDay %= $lengthOfWeek; $weekTranslate = $langs->trans("DurationWeek"); if ($sWeek >= 2) { $weekTranslate = $langs->trans("DurationWeeks"); } $sTime .= $sWeek.' '.$weekTranslate.' '; } } } if ($sDay > 0) { $dayTranslate = $langs->trans("Day"); if ($sDay > 1) { $dayTranslate = $langs->trans("Days"); } $sTime .= $sDay.' '.$langs->trans("d").' '; } if ($format == 'all') { if ($iSecond || empty($sDay)) { $sTime .= dol_print_date($iSecond, 'hourduration', true); } } elseif ($format == 'allhourminsec') { return sprintf("%02d", ($sWeek * $lengthOfWeek * $nbHbyDay + $sDay * $nbHbyDay + (int) floor($iSecond / 3600))).':'.sprintf("%02d", ((int) floor(($iSecond % 3600) / 60))).':'.sprintf("%02d", ((int) ($iSecond % 60))); } elseif ($format == 'allhourmin') { return sprintf("%02d", ($sWeek * $lengthOfWeek * $nbHbyDay + $sDay * $nbHbyDay + (int) floor($iSecond / 3600))).':'.sprintf("%02d", ((int) floor(($iSecond % 3600) / 60))); } elseif ($format == 'allhour') { return sprintf("%02d", ($sWeek * $lengthOfWeek * $nbHbyDay + $sDay * $nbHbyDay + (int) floor($iSecond / 3600))); } } elseif ($format == 'hour') { // only hour part $sTime = dol_print_date($iSecond, '%H', true); } elseif ($format == 'fullhour') { if (!empty($iSecond)) { $iSecond /= 3600; } else { $iSecond = 0; } $sTime = $iSecond; } elseif ($format == 'min') { // only min part $sTime = dol_print_date($iSecond, '%M', true); } elseif ($format == 'sec') { // only sec part $sTime = dol_print_date($iSecond, '%S', true); } elseif ($format == 'month') { // only month part $sTime = dol_print_date($iSecond, '%m', true); } elseif ($format == 'year') { // only year part $sTime = dol_print_date($iSecond, '%Y', true); } return trim($sTime); } /** Convert duration to hour * * @param int $duration_value Duration value * @param string $duration_unit Duration unit * @return int $result */ function convertDurationtoHour($duration_value, $duration_unit) { $result = 0; if ($duration_unit == 's') { $result = $duration_value / 3600; } if ($duration_unit == 'i') { $result = $duration_value / 60; } if ($duration_unit == 'h') { $result = $duration_value; } if ($duration_unit == 'd') { $result = $duration_value * 24; } if ($duration_unit == 'w') { $result = $duration_value * 24 * 7; } if ($duration_unit == 'm') { $result = $duration_value * 730.484; } if ($duration_unit == 'y') { $result = $duration_value * 365 * 24; } return $result; } /** * Generate a SQL string to make a filter into a range (for second of date until last second of date). * This method allows to maje SQL request that will deal correctly the timezone of server. * * @param string $datefield Name of SQL field where apply sql date filter * @param int|string $day_date Day date (Can be 0 or '' for filter on a month) * @param int|string $month_date Month date (Can be 0 or '' for filter on a year) * @param int|string $year_date Year date * @param int $excludefirstand Exclude first and * @param mixed $gm False or 0 or 'tzserver' = Input date fields are date info in the server TZ. True or 1 or 'gmt' = Input are date info in GMT TZ. * Note: In database, dates are always for the server TZ. * @return string $sqldate String with SQL filter * @see forgeSQLFromUniversalSearchCriteria() * @see natural_search() */ function dolSqlDateFilter($datefield, $day_date, $month_date, $year_date, $excludefirstand = 0, $gm = false) { global $db; $sqldate = ''; $day_date = intval($day_date); $month_date = intval($month_date); $year_date = intval($year_date); if ($month_date > 0) { if ($month_date > 12) { // protection for bad value of month return " AND 1 = 2"; } if ($year_date > 0 && empty($day_date)) { $sqldate .= ($excludefirstand ? "" : " AND ").$datefield." BETWEEN '".$db->idate(dol_get_first_day($year_date, $month_date, $gm)); $sqldate .= "' AND '".$db->idate(dol_get_last_day($year_date, $month_date, $gm))."'"; } elseif ($year_date > 0 && !empty($day_date)) { $sqldate .= ($excludefirstand ? "" : " AND ").$datefield." BETWEEN '".$db->idate(dol_mktime(0, 0, 0, $month_date, $day_date, $year_date, $gm)); $sqldate .= "' AND '".$db->idate(dol_mktime(23, 59, 59, $month_date, $day_date, $year_date, $gm))."'"; } else { // This case is not reliable on TZ, but we should not need it. $sqldate .= ($excludefirstand ? "" : " AND ")." date_format( ".$datefield.", '%c') = '".$db->escape($month_date)."'"; } } elseif ($year_date > 0) { $sqldate .= ($excludefirstand ? "" : " AND ").$datefield." BETWEEN '".$db->idate(dol_get_first_day($year_date, 1, $gm)); $sqldate .= "' AND '".$db->idate(dol_get_last_day($year_date, 12, $gm))."'"; } return $sqldate; } /** * Convert a string date into a GM Timestamps date * Warning: YYYY-MM-DDTHH:MM:SS+02:00 (RFC3339) is not supported. If parameter gm is 1, we will use no TZ, if not we will use TZ of server, not the one inside string. * * @param string $string Date in a string * YYYYMMDD * YYYYMMDDHHMMSS * YYYYMMDDTHHMMSSZ * YYYY-MM-DDTHH:MM:SSZ (RFC3339) * DD/MM/YY or DD/MM/YYYY (deprecated) * DD/MM/YY HH:MM:SS or DD/MM/YYYY HH:MM:SS (deprecated) * @param int|string $gm 'gmt' or 1 =Input date is GM date, * 'tzserver' or 0 =Input date is date using PHP server timezone * @return int Date as a timestamp * 19700101020000 -> 7200 with gm=1 * 19700101000000 -> 0 with gm=1 * * @see dol_print_date(), dol_mktime(), dol_getdate() */ function dol_stringtotime($string, $gm = 1) { $reg = array(); // Convert date with format DD/MM/YYY HH:MM:SS. This part of code should not be used. if (preg_match('/^([0-9]+)\/([0-9]+)\/([0-9]+)\s?([0-9]+)?:?([0-9]+)?:?([0-9]+)?/i', $string, $reg)) { dol_syslog("dol_stringtotime call to function with deprecated parameter format", LOG_WARNING); // Date est au format 'DD/MM/YY' ou 'DD/MM/YY HH:MM:SS' // Date est au format 'DD/MM/YYYY' ou 'DD/MM/YYYY HH:MM:SS' $sday = (int) $reg[1]; $smonth = (int) $reg[2]; $syear = (int) $reg[3]; $shour = (int) $reg[4]; $smin = (int) $reg[5]; $ssec = (int) $reg[6]; if ($syear < 50) { $syear += 1900; } if ($syear >= 50 && $syear < 100) { $syear += 2000; } $string = sprintf("%04d%02d%02d%02d%02d%02d", $syear, $smonth, $sday, $shour, $smin, $ssec); } elseif (preg_match('/^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})Z$/i', $string, $reg) // Convert date with format YYYY-MM-DDTHH:MM:SSZ (RFC3339) || preg_match('/^([0-9]{4})-([0-9]{2})-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})$/i', $string, $reg) // Convert date with format YYYY-MM-DD HH:MM:SS || preg_match('/^([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z$/i', $string, $reg) // Convert date with format YYYYMMDDTHHMMSSZ ) { $syear = (int) $reg[1]; $smonth = (int) $reg[2]; $sday = (int) $reg[3]; $shour = (int) $reg[4]; $smin = (int) $reg[5]; $ssec = (int) $reg[6]; $string = sprintf("%04d%02d%02d%02d%02d%02d", $syear, $smonth, $sday, $shour, $smin, $ssec); } $string = preg_replace('/([^0-9])/i', '', $string); $tmp = $string.'000000'; // Clean $gm if ($gm === 1) { $gm = 'gmt'; } elseif (empty($gm) || $gm === 'tzserver') { $gm = 'tzserver'; } $date = dol_mktime((int) substr($tmp, 8, 2), (int) substr($tmp, 10, 2), (int) substr($tmp, 12, 2), (int) substr($tmp, 4, 2), (int) substr($tmp, 6, 2), (int) substr($tmp, 0, 4), $gm); return $date; } /** * Return previous day * * @param int $day Day * @param int $month Month * @param int $year Year * @return array Previous year,month,day */ function dol_get_prev_day($day, $month, $year) { $time = dol_mktime(12, 0, 0, $month, $day, $year, 1, 0); $time -= 24 * 60 * 60; $tmparray = dol_getdate($time, true); return array('year' => $tmparray['year'], 'month' => $tmparray['mon'], 'day' => $tmparray['mday']); } /** * Return next day * * @param int $day Day * @param int $month Month * @param int $year Year * @return array Next year,month,day */ function dol_get_next_day($day, $month, $year) { $time = dol_mktime(12, 0, 0, $month, $day, $year, 1, 0); $time += 24 * 60 * 60; $tmparray = dol_getdate($time, true); return array('year' => $tmparray['year'], 'month' => $tmparray['mon'], 'day' => $tmparray['mday']); } /** * Return previous month * * @param int $month Month * @param int $year Year * @return array Previous year,month */ function dol_get_prev_month($month, $year) { if ($month == 1) { $prev_month = 12; $prev_year = $year - 1; } else { $prev_month = $month - 1; $prev_year = $year; } return array('year' => $prev_year, 'month' => $prev_month); } /** * Return next month * * @param int $month Month * @param int $year Year * @return array Next year,month */ function dol_get_next_month($month, $year) { if ($month == 12) { $next_month = 1; $next_year = $year + 1; } else { $next_month = $month + 1; $next_year = $year; } return array('year' => $next_year, 'month' => $next_month); } /** * Return previous week * * @param int $day Day * @param int $week Week * @param int $month Month * @param int $year Year * @return array Previous year,month,day */ function dol_get_prev_week($day, $week, $month, $year) { $tmparray = dol_get_first_day_week($day, $month, $year); $time = dol_mktime(12, 0, 0, $month, $tmparray['first_day'], $year, 1, 0); $time -= 24 * 60 * 60 * 7; $tmparray = dol_getdate($time, true); return array('year' => $tmparray['year'], 'month' => $tmparray['mon'], 'day' => $tmparray['mday']); } /** * Return next week * * @param int $day Day * @param int $week Week * @param int $month Month * @param int $year Year * @return array Next year,month,day */ function dol_get_next_week($day, $week, $month, $year) { $tmparray = dol_get_first_day_week($day, $month, $year); $time = dol_mktime(12, 0, 0, $tmparray['first_month'], $tmparray['first_day'], $tmparray['first_year'], 1, 0); $time += 24 * 60 * 60 * 7; $tmparray = dol_getdate($time, true); return array('year' => $tmparray['year'], 'month' => $tmparray['mon'], 'day' => $tmparray['mday']); } /** * Return GMT time for first day of a month or year * * @param int $year Year * @param int $month Month * @param bool|int|string $gm False or 0 or 'tzserver' = Return date to compare with server TZ, * True or 1 or 'gmt' to compare with GMT date. * Example: dol_get_first_day(1970,1,false) will return -3600 with TZ+1, a dol_print_date on it will return 1970-01-01 00:00:00 * Example: dol_get_first_day(1970,1,true) will return 0 whatever is TZ, a dol_print_date on it will return 1970-01-01 00:00:00 * @return int|string Date as a timestamp, '' if error */ function dol_get_first_day($year, $month = 1, $gm = false) { if ($year > 9999) { return ''; } return dol_mktime(0, 0, 0, $month, 1, $year, $gm); } /** * Return GMT time for last day of a month or year. * Note: The timestamp contains last day and last hours (23:59:59) * * @param int $year Year * @param int $month Month * @param bool|int|string $gm False or 0 or 'tzserver' = Return date to compare with server TZ, * True or 1 or 'gmt' to compare with GMT date. * @return int|string Date as a timestamp, '' if error */ function dol_get_last_day($year, $month = 12, $gm = false) { if ($year > 9999) { return ''; } if ($month == 12) { $month = 1; $year += 1; } else { $month += 1; } // On se deplace au debut du mois suivant, et on retire un jour $datelim = dol_mktime(23, 59, 59, $month, 1, $year, $gm); $datelim -= (3600 * 24); return $datelim; } /** * Return GMT time for last hour of a given GMT date (it replaces hours, min and second part to 23:59:59) * * @param int $date Date GMT * @param bool|int|string $gm False or 0 or 'tzserver' = Return date to compare with server TZ, * 'gmt' to compare with GMT date. * @return int Date for last hour of a given date */ function dol_get_last_hour($date, $gm = 'tzserver') { $tmparray = dol_getdate($date, false, ($gm == 'gmt' ? 'gmt' : '')); return dol_mktime(23, 59, 59, $tmparray['mon'], $tmparray['mday'], $tmparray['year'], $gm); } /** * Return GMT time for first hour of a given GMT date (it removes hours, min and second part) * * @param int $date Date GMT * @param bool|int|string $gm False or 0 or 'tzserver' = Return date to compare with server TZ, * 'gmt' to compare with GMT date. * @return int Date for first hour of a given date */ function dol_get_first_hour($date, $gm = 'tzserver') { $tmparray = dol_getdate($date, false, ($gm == 'gmt' ? 'gmt' : '')); return dol_mktime(0, 0, 0, $tmparray['mon'], $tmparray['mday'], $tmparray['year'], $gm); } /** Return first day of week for a date. First day of week may be monday if option MAIN_START_WEEK is 1. * * @param int $day Day * @param int $month Month * @param int $year Year * @param bool|int|string $gm False or 0 or 'tzserver' = Return date to compare with server TZ, * True or 1 or 'gmt' to compare with GMT date. * @return array year,month,week,first_day,first_month,first_year,prev_day,prev_month,prev_year */ function dol_get_first_day_week($day, $month, $year, $gm = false) { global $conf; //$day=2; $month=2; $year=2015; $date = dol_mktime(0, 0, 0, $month, $day, $year, $gm); //Checking conf of start week $start_week = (isset($conf->global->MAIN_START_WEEK) ? $conf->global->MAIN_START_WEEK : 1); $tmparray = dol_getdate($date, true); // detail of current day //Calculate days = offset from current day $days = $start_week - $tmparray['wday']; if ($days >= 1) { $days = 7 - $days; } $days = abs($days); $seconds = $days * 24 * 60 * 60; //print 'start_week='.$start_week.' tmparray[wday]='.$tmparray['wday'].' day offset='.$days.' seconds offset='.$seconds.'
'; //Get first day of week $tmpdaytms = (int) date((string) $tmparray['0']) - $seconds; // $tmparray[0] is day of parameters $tmpday = idate("d", $tmpdaytms); //Check first day of week is in same month than current day or not if ($tmpday > $day) { $prev_month = $month - 1; $prev_year = $year; if ($prev_month == 0) { $prev_month = 12; $prev_year = $year - 1; } } else { $prev_month = $month; $prev_year = $year; } $tmpmonth = $prev_month; $tmpyear = $prev_year; //Get first day of next week $tmptime = dol_mktime(12, 0, 0, $month, $tmpday, $year, 1, 0); $tmptime -= 24 * 60 * 60 * 7; $tmparray = dol_getdate($tmptime, true); $prev_day = $tmparray['mday']; //Check prev day of week is in same month than first day or not if ($prev_day > $tmpday) { $prev_month = $month - 1; $prev_year = $year; if ($prev_month == 0) { $prev_month = 12; $prev_year = $year - 1; } } $week = date("W", dol_mktime(0, 0, 0, $tmpmonth, $tmpday, $tmpyear, $gm)); return array('year' => $year, 'month' => $month, 'week' => $week, 'first_day' => $tmpday, 'first_month' => $tmpmonth, 'first_year' => $tmpyear, 'prev_year' => $prev_year, 'prev_month' => $prev_month, 'prev_day' => $prev_day); } /** * Return the easter day in GMT time. * This function replaces easter_date() that returns a date in local TZ. * * @param int $year Year * @return int GMT Date of easter day */ function getGMTEasterDatetime($year) { $base = new DateTime("$year-03-21", new DateTimeZone("UTC")); $days = easter_days($year); // Return number of days between 21 march and easter day. $tmp = $base->add(new DateInterval("P{$days}D")); return $tmp->getTimestamp(); } /** * Return the number of non working days including Friday, Saturday and Sunday (or not) between 2 dates in timestamp. * Dates must be UTC with hour, min, sec to 0. * Called by function num_open_day() * * @param int $timestampStart Timestamp start (UTC with hour, min, sec = 0) * @param int $timestampEnd Timestamp end (UTC with hour, min, sec = 0) * @param string $country_code Country code * @param int $lastday Last day is included, 0: no, 1:yes * @param int $includesaturday Include saturday as non working day (-1=use setup, 0=no, 1=yes) * @param int $includesunday Include sunday as non working day (-1=use setup, 0=no, 1=yes) * @param int $includefriday Include friday as non working day (-1=use setup, 0=no, 1=yes) * @param int $includemonday Include monday as non working day (-1=use setup, 0=no, 1=yes) * @return int|string Number of non working days or error message string if error * @see num_between_day(), num_open_day() */ function num_public_holiday($timestampStart, $timestampEnd, $country_code = '', $lastday = 0, $includesaturday = -1, $includesunday = -1, $includefriday = -1, $includemonday = -1) { global $db, $mysoc; $nbFerie = 0; // Check to ensure we use correct parameters if (($timestampEnd - $timestampStart) % 86400 != 0) { return 'Error Dates must use same hours and must be GMT dates'; } if (empty($country_code)) { $country_code = $mysoc->country_code; } if ($includemonday < 0) { $includemonday = getDolGlobalInt('MAIN_NON_WORKING_DAYS_INCLUDE_MONDAY', 0); } if ($includefriday < 0) { $includefriday = getDolGlobalInt('MAIN_NON_WORKING_DAYS_INCLUDE_FRIDAY', 0); } if ($includesaturday < 0) { $includesaturday = getDolGlobalInt('MAIN_NON_WORKING_DAYS_INCLUDE_SATURDAY', 1); } if ($includesunday < 0) { $includesunday = getDolGlobalInt('MAIN_NON_WORKING_DAYS_INCLUDE_SUNDAY', 1); } $country_id = dol_getIdFromCode($db, $country_code, 'c_country', 'code', 'rowid'); $i = 0; while ((($lastday == 0 && $timestampStart < $timestampEnd) || ($lastday && $timestampStart <= $timestampEnd)) && ($i < 50000)) { // Loop end when equals (Test on i is a security loop to avoid infinite loop) $ferie = false; $specialdayrule = array(); $jour = (int) gmdate("d", $timestampStart); $mois = (int) gmdate("m", $timestampStart); $annee = (int) gmdate("Y", $timestampStart); //print "jour=".$jour." month=".$mois." year=".$annee." includesaturday=".$includesaturday." includesunday=".$includesunday."\n"; // Loop on public holiday defined into hrm_public_holiday for the day, month and year analyzed // TODO Execute this request first and store results into an array, then reuse this array. $sql = "SELECT code, entity, fk_country, dayrule, year, month, day, active"; $sql .= " FROM ".MAIN_DB_PREFIX."c_hrm_public_holiday"; $sql .= " WHERE active = 1 and fk_country IN (0".($country_id > 0 ? ", ".$country_id : 0).")"; $sql .= " AND entity IN (0," .getEntity('holiday') .")"; $resql = $db->query($sql); if ($resql) { $num_rows = $db->num_rows($resql); $i = 0; while ($i < $num_rows) { $obj = $db->fetch_object($resql); if (!empty($obj->dayrule) && $obj->dayrule != 'date') { // For example 'easter', '...' $specialdayrule[$obj->dayrule] = $obj->dayrule; } else { $match = 1; if (!empty($obj->year) && $obj->year != $annee) { $match = 0; } if ($obj->month != $mois) { $match = 0; } if ($obj->day != $jour) { $match = 0; } if ($match) { $ferie = true; } } $i++; } } else { dol_syslog($db->lasterror(), LOG_ERR); return 'Error sql '.$db->lasterror(); } //var_dump($specialdayrule)."\n"; //print "ferie=".$ferie."\n"; if (!$ferie) { // Special dayrules if (in_array('easter', $specialdayrule)) { // Calculation for easter date $date_paques = getGMTEasterDatetime($annee); $jour_paques = gmdate("d", $date_paques); $mois_paques = gmdate("m", $date_paques); if ($jour_paques == $jour && $mois_paques == $mois) { $ferie = true; } // Easter (sunday) } if (in_array('eastermonday', $specialdayrule)) { // Calculation for the monday of easter date $date_paques = getGMTEasterDatetime($annee); //print 'PPP'.$date_paques.' '.dol_print_date($date_paques, 'dayhour', 'gmt')." "; $date_lundi_paques = $date_paques + (3600 * 24); $jour_lundi_paques = gmdate("d", $date_lundi_paques); $mois_lundi_paques = gmdate("m", $date_lundi_paques); if ($jour_lundi_paques == $jour && $mois_lundi_paques == $mois) { $ferie = true; } // Easter (monday) //print 'annee='.$annee.' $jour='.$jour.' $mois='.$mois.' $jour_lundi_paques='.$jour_lundi_paques.' $mois_lundi_paques='.$mois_lundi_paques."\n"; } //Good Friday if (in_array('goodfriday', $specialdayrule)) { // Pulls the date of Easter $easter = getGMTEasterDatetime($annee); // Calculates the date of Good Friday based on Easter $date_good_friday = $easter - (2 * 3600 * 24); $dom_good_friday = gmdate("d", $date_good_friday); $month_good_friday = gmdate("m", $date_good_friday); if ($dom_good_friday == $jour && $month_good_friday == $mois) { $ferie = true; } } if (in_array('ascension', $specialdayrule)) { // Calcul du jour de l'ascension (39 days after easter day) $date_paques = getGMTEasterDatetime($annee); $date_ascension = $date_paques + (3600 * 24 * 39); $jour_ascension = gmdate("d", $date_ascension); $mois_ascension = gmdate("m", $date_ascension); if ($jour_ascension == $jour && $mois_ascension == $mois) { $ferie = true; } // Ascension (thursday) } if (in_array('pentecost', $specialdayrule)) { // Calculation of "Pentecote" (49 days after easter day) $date_paques = getGMTEasterDatetime($annee); $date_pentecote = $date_paques + (3600 * 24 * 49); $jour_pentecote = gmdate("d", $date_pentecote); $mois_pentecote = gmdate("m", $date_pentecote); if ($jour_pentecote == $jour && $mois_pentecote == $mois) { $ferie = true; } // "Pentecote" (sunday) } if (in_array('pentecotemonday', $specialdayrule)) { // Calculation of "Pentecote" (49 days after easter day) $date_paques = getGMTEasterDatetime($annee); $date_pentecote = $date_paques + (3600 * 24 * 50); $jour_pentecote = gmdate("d", $date_pentecote); $mois_pentecote = gmdate("m", $date_pentecote); if ($jour_pentecote == $jour && $mois_pentecote == $mois) { $ferie = true; } // "Pentecote" (monday) } if (in_array('viernessanto', $specialdayrule)) { // Viernes Santo $date_paques = getGMTEasterDatetime($annee); $date_viernes = $date_paques - (3600 * 24 * 2); $jour_viernes = gmdate("d", $date_viernes); $mois_viernes = gmdate("m", $date_viernes); if ($jour_viernes == $jour && $mois_viernes == $mois) { $ferie = true; } //Viernes Santo } if (in_array('fronleichnam', $specialdayrule)) { // Fronleichnam (60 days after easter sunday) $date_paques = getGMTEasterDatetime($annee); $date_fronleichnam = $date_paques + (3600 * 24 * 60); $jour_fronleichnam = gmdate("d", $date_fronleichnam); $mois_fronleichnam = gmdate("m", $date_fronleichnam); if ($jour_fronleichnam == $jour && $mois_fronleichnam == $mois) { $ferie = true; } // Fronleichnam } if (in_array('genevafast', $specialdayrule)) { // Geneva fast in Switzerland (Thursday after the first sunday in September) $date_1sunsept = strtotime('next thursday', strtotime('next sunday', mktime(0, 0, 0, 9, 1, $annee))); $jour_1sunsept = date("d", $date_1sunsept); $mois_1sunsept = date("m", $date_1sunsept); if ($jour_1sunsept == $jour && $mois_1sunsept == $mois) { $ferie = true; } // Geneva fast in Switzerland } } //print "ferie=".$ferie."\n"; // If we have to include Friday, Saturday and Sunday if (!$ferie) { if ($includefriday || $includesaturday || $includesunday) { $jour_julien = unixtojd($timestampStart); $jour_semaine = jddayofweek($jour_julien, 0); if ($includefriday) { //Friday (5), Saturday (6) and Sunday (0) if ($jour_semaine == 5) { $ferie = true; } } if ($includesaturday) { //Friday (5), Saturday (6) and Sunday (0) if ($jour_semaine == 6) { $ferie = true; } } if ($includesunday) { //Friday (5), Saturday (6) and Sunday (0) if ($jour_semaine == 0) { $ferie = true; } } } } //print "ferie=".$ferie."\n"; // We increase the counter of non working day if ($ferie) { $nbFerie++; } // Increase number of days (on go up into loop) $timestampStart = dol_time_plus_duree($timestampStart, 1, 'd'); //var_dump($jour.' '.$mois.' '.$annee.' '.$timestampStart); $i++; } //print "nbFerie=".$nbFerie."\n"; return $nbFerie; } /** * Function to return number of days between two dates (date must be UTC date !) * Example: 2012-01-01 2012-01-02 => 1 if lastday=0, 2 if lastday=1 * * @param int $timestampStart Timestamp start UTC * @param int $timestampEnd Timestamp end UTC * @param int $lastday Last day is included, 0: no, 1:yes * @return int Number of days * @see num_public_holiday(), num_open_day() */ function num_between_day($timestampStart, $timestampEnd, $lastday = 0) { if ($timestampStart < $timestampEnd) { if ($lastday == 1) { $bit = 0; } else { $bit = 1; } $nbjours = (int) floor(($timestampEnd - $timestampStart) / (60 * 60 * 24)) + 1 - $bit; } else { $nbjours = 0; } //print ($timestampEnd - $timestampStart) - $lastday; return $nbjours; } /** * Function to return number of working days (and text of units) between two dates (working days) * * @param int $timestampStart Timestamp for start date (date must be UTC to avoid calculation errors) * @param int $timestampEnd Timestamp for end date (date must be UTC to avoid calculation errors) * @param int $inhour 0: return number of days, 1: return number of hours * @param int $lastday We include last day, 0: no, 1:yes * @param int $halfday Tag to define half day when holiday start and end * @param string $country_code Country code (company country code if not defined) * @return int|string Number of days or hours or string if error * @seealso num_between_day(), num_public_holiday() */ function num_open_day($timestampStart, $timestampEnd, $inhour = 0, $lastday = 0, $halfday = 0, $country_code = '') { global $langs, $mysoc; if (empty($country_code)) { $country_code = $mysoc->country_code; } dol_syslog('num_open_day timestampStart='.$timestampStart.' timestampEnd='.$timestampEnd.' bit='.$lastday.' country_code='.$country_code); // Check parameters if (!is_int($timestampStart) && !is_float($timestampStart)) { return 'ErrorBadParameter_num_open_day'; } if (!is_int($timestampEnd) && !is_float($timestampEnd)) { return 'ErrorBadParameter_num_open_day'; } //print 'num_open_day timestampStart='.$timestampStart.' timestampEnd='.$timestampEnd.' bit='.$lastday; if ($timestampStart < $timestampEnd) { $numdays = num_between_day($timestampStart, $timestampEnd, $lastday); $numholidays = num_public_holiday($timestampStart, $timestampEnd, $country_code, $lastday); $nbOpenDay = ($numdays - $numholidays); if ($inhour == 1 && $nbOpenDay <= 3) { $nbOpenDay *= 24; } return $nbOpenDay - (($inhour == 1 ? 12 : 0.5) * abs($halfday)); } elseif ($timestampStart == $timestampEnd) { $numholidays = 0; if ($lastday) { $numholidays = num_public_holiday($timestampStart, $timestampEnd, $country_code, $lastday); if ($numholidays == 1) { return 0; } } $nbOpenDay = $lastday; if ($inhour == 1) { $nbOpenDay *= 24; } return $nbOpenDay - (($inhour == 1 ? 12 : 0.5) * abs($halfday)); } else { return $langs->trans("Error"); } } /** * Return array of translated months or selected month. * This replace old function monthArrayOrSelected. * * @param Translate $outputlangs Object langs * @param int $short 0=Return long label, 1=Return short label * @return array,string> String of months in normal or short string format */ function monthArray($outputlangs, $short = 0) { $montharray = array( 1 => $outputlangs->trans("Month01"), 2 => $outputlangs->trans("Month02"), 3 => $outputlangs->trans("Month03"), 4 => $outputlangs->trans("Month04"), 5 => $outputlangs->trans("Month05"), 6 => $outputlangs->trans("Month06"), 7 => $outputlangs->trans("Month07"), 8 => $outputlangs->trans("Month08"), 9 => $outputlangs->trans("Month09"), 10 => $outputlangs->trans("Month10"), 11 => $outputlangs->trans("Month11"), 12 => $outputlangs->trans("Month12") ); if (!empty($short)) { $montharray = array( 1 => $outputlangs->trans("MonthShort01"), 2 => $outputlangs->trans("MonthShort02"), 3 => $outputlangs->trans("MonthShort03"), 4 => $outputlangs->trans("MonthShort04"), 5 => $outputlangs->trans("MonthShort05"), 6 => $outputlangs->trans("MonthShort06"), 7 => $outputlangs->trans("MonthShort07"), 8 => $outputlangs->trans("MonthShort08"), 9 => $outputlangs->trans("MonthShort09"), 10 => $outputlangs->trans("MonthShort10"), 11 => $outputlangs->trans("MonthShort11"), 12 => $outputlangs->trans("MonthShort12") ); } return $montharray; } /** * Return array of week numbers. * * @param int $month Month number * @param int $year Year number * @return string[] Week numbers (week 1 is '01') */ function getWeekNumbersOfMonth($month, $year) { $nb_days = cal_days_in_month(CAL_GREGORIAN, $month, $year); $TWeek = array(); for ($day = 1; $day < $nb_days; $day++) { $week_number = getWeekNumber($day, $month, $year); $TWeek[$week_number] = $week_number; } return $TWeek; } /** * Return array of first day of weeks. * * @param string[] $TWeek array of week numbers we want (week 1 must be '01') * @param int $year Year number * @return string[] First day of each week in entry (day 1 is '01') */ function getFirstDayOfEachWeek($TWeek, $year) { $TFirstDayOfWeek = array(); foreach ($TWeek as $weekNb) { if (in_array('01', $TWeek) && in_array('52', $TWeek) && $weekNb == '01') { $year++; //Si on a la 1re semaine et la semaine 52 c'est qu'on change d'année } $TFirstDayOfWeek[$weekNb] = date('d', strtotime($year.'W'.$weekNb)); } return $TFirstDayOfWeek; } /** * Return array of last day of weeks. * * @param string[] $TWeek array of week numbers * @param int $year Year number * @return string[] Last day of week (day 1 is '01') */ function getLastDayOfEachWeek($TWeek, $year) { $TLastDayOfWeek = array(); foreach ($TWeek as $weekNb) { $TLastDayOfWeek[$weekNb] = date('d', strtotime($year.'W'.$weekNb.'+6 days')); } return $TLastDayOfWeek; } /** * Return week number. * * @param int $day Day number * @param int $month Month number * @param int $year Year number * @return string Week number as two digits (week 1 is '01') */ function getWeekNumber($day, $month, $year) { $date = new DateTime($year.'-'.$month.'-'.$day); $week = $date->format("W"); return $week; }