1<?php 2/* Copyright (C) 2004-2011 Laurent Destailleur <eldy@users.sourceforge.net> 3 * Copyright (C) 2005-2011 Regis Houssin <regis.houssin@inodbox.com> 4 * Copyright (C) 2011-2015 Juanjo Menent <jmenent@2byte.es> 5 * Copyright (C) 2017 Ferran Marcet <fmarcet@2byte.es> 6 * Copyright (C) 2018 Charlene Benke <charlie@patas-monkey.com> 7* 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 3 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program. If not, see <https://www.gnu.org/licenses/>. 20 * or see https://www.gnu.org/ 21 */ 22 23/** 24 * \file htdocs/core/lib/date.lib.php 25 * \brief Set of function to manipulate dates 26 */ 27 28 29/** 30 * Return an array with timezone values 31 * 32 * @return array Array with timezone values 33 */ 34function get_tz_array() 35{ 36 $tzarray = array( 37 -11=>"Pacific/Midway", 38 -10=>"Pacific/Fakaofo", 39 -9=>"America/Anchorage", 40 -8=>"America/Los_Angeles", 41 -7=>"America/Dawson_Creek", 42 -6=>"America/Chicago", 43 -5=>"America/Bogota", 44 -4=>"America/Anguilla", 45 -3=>"America/Araguaina", 46 -2=>"America/Noronha", 47 -1=>"Atlantic/Azores", 48 0=>"Africa/Abidjan", 49 1=>"Europe/Paris", 50 2=>"Europe/Helsinki", 51 3=>"Europe/Moscow", 52 4=>"Asia/Dubai", 53 5=>"Asia/Karachi", 54 6=>"Indian/Chagos", 55 7=>"Asia/Jakarta", 56 8=>"Asia/Hong_Kong", 57 9=>"Asia/Tokyo", 58 10=>"Australia/Sydney", 59 11=>"Pacific/Noumea", 60 12=>"Pacific/Auckland", 61 13=>"Pacific/Enderbury" 62 ); 63 return $tzarray; 64} 65 66 67/** 68 * Return server timezone string 69 * 70 * @return string PHP server timezone string ('Europe/Paris') 71 */ 72function getServerTimeZoneString() 73{ 74 return @date_default_timezone_get(); 75} 76 77/** 78 * Return server timezone int. 79 * 80 * @param string $refgmtdate Reference period for timezone (timezone differs on winter and summer. May be 'now', 'winter' or 'summer') 81 * @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. 82 */ 83function getServerTimeZoneInt($refgmtdate = 'now') 84{ 85 if (method_exists('DateTimeZone', 'getOffset')) 86 { 87 // Method 1 (include daylight) 88 $gmtnow = dol_now('gmt'); $yearref = dol_print_date($gmtnow, '%Y'); $monthref = dol_print_date($gmtnow, '%m'); $dayref = dol_print_date($gmtnow, '%d'); 89 if ($refgmtdate == 'now') $newrefgmtdate = $yearref.'-'.$monthref.'-'.$dayref; 90 elseif ($refgmtdate == 'summer') $newrefgmtdate = $yearref.'-08-01'; 91 else $newrefgmtdate = $yearref.'-01-01'; 92 $newrefgmtdate .= 'T00:00:00+00:00'; 93 $localtz = new DateTimeZone(getServerTimeZoneString()); 94 $localdt = new DateTime($newrefgmtdate, $localtz); 95 $tmp = -1 * $localtz->getOffset($localdt); 96 //print $refgmtdate.'='.$tmp; 97 } else { 98 $tmp = 0; 99 dol_print_error('', 'PHP version must be 5.3+'); 100 } 101 $tz = round(($tmp < 0 ? 1 : -1) * abs($tmp / 3600)); 102 return $tz; 103} 104 105 106/** 107 * Add a delay to a date 108 * 109 * @param int $time Date timestamp (or string with format YYYY-MM-DD) 110 * @param int $duration_value Value of delay to add 111 * @param int $duration_unit Unit of added delay (d, m, y, w, h, i) 112 * @return int New timestamp 113 */ 114function dol_time_plus_duree($time, $duration_value, $duration_unit) 115{ 116 global $conf; 117 118 if ($duration_value == 0) return $time; 119 if ($duration_unit == 'i') return $time + (60 * $duration_value); 120 if ($duration_unit == 'h') return $time + (3600 * $duration_value); 121 if ($duration_unit == 'w') return $time + (3600 * 24 * 7 * $duration_value); 122 123 $deltastring = 'P'; 124 125 if ($duration_value > 0) { $deltastring .= abs($duration_value); $sub = false; } 126 if ($duration_value < 0) { $deltastring .= abs($duration_value); $sub = true; } 127 if ($duration_unit == 'd') { $deltastring .= "D"; } 128 if ($duration_unit == 'm') { $deltastring .= "M"; } 129 if ($duration_unit == 'y') { $deltastring .= "Y"; } 130 131 $date = new DateTime(); 132 if (!empty($conf->global->MAIN_DATE_IN_MEMORY_ARE_GMT)) $date->setTimezone(new DateTimeZone('UTC')); 133 $date->setTimestamp($time); 134 $interval = new DateInterval($deltastring); 135 136 if ($sub) $date->sub($interval); 137 else $date->add($interval); 138 139 return $date->getTimestamp(); 140} 141 142 143/** 144 * Convert hours and minutes into seconds 145 * 146 * @param int $iHours Hours 147 * @param int $iMinutes Minutes 148 * @param int $iSeconds Seconds 149 * @return int Time into seconds 150 * @see convertSecondToTime() 151 */ 152function convertTime2Seconds($iHours = 0, $iMinutes = 0, $iSeconds = 0) 153{ 154 $iResult = ($iHours * 3600) + ($iMinutes * 60) + $iSeconds; 155 return $iResult; 156} 157 158 159/** Return, in clear text, value of a number of seconds in days, hours and minutes. 160 * Can be used to show a duration. 161 * 162 * @param int $iSecond Number of seconds 163 * @param string $format Output format ('all': total delay days hour:min like "2 days 12:30", 164 * - 'allwithouthour': total delay days without hour part like "2 days", 165 * - 'allhourmin': total delay with format hours:min like "60:30", 166 * - 'allhourminsec': total delay with format hours:min:sec like "60:30:10", 167 * - 'allhour': total delay hours without min/sec like "60:30", 168 * - 'fullhour': total delay hour decimal like "60.5" for 60:30, 169 * - 'hour': only hours part "12", 170 * - 'min': only minutes part "30", 171 * - 'sec': only seconds part, 172 * - 'month': only month part, 173 * - 'year': only year part); 174 * @param int $lengthOfDay Length of day (default 86400 seconds for 1 day, 28800 for 8 hour) 175 * @param int $lengthOfWeek Length of week (default 7) 176 * @return string Formated text of duration 177 * Example: 0 return 00:00, 3600 return 1:00, 86400 return 1d, 90000 return 1 Day 01:00 178 * @see convertTime2Seconds() 179 */ 180function convertSecondToTime($iSecond, $format = 'all', $lengthOfDay = 86400, $lengthOfWeek = 7) 181{ 182 global $langs; 183 184 if (empty($lengthOfDay)) $lengthOfDay = 86400; // 1 day = 24 hours 185 if (empty($lengthOfWeek)) $lengthOfWeek = 7; // 1 week = 7 days 186 187 if ($format == 'all' || $format == 'allwithouthour' || $format == 'allhour' || $format == 'allhourmin' || $format == 'allhourminsec') 188 { 189 if ((int) $iSecond === 0) return '0'; // This is to avoid having 0 return a 12:00 AM for en_US 190 191 $sTime = ''; 192 $sDay = 0; 193 $sWeek = 0; 194 195 if ($iSecond >= $lengthOfDay) 196 { 197 for ($i = $iSecond; $i >= $lengthOfDay; $i -= $lengthOfDay) 198 { 199 $sDay++; 200 $iSecond -= $lengthOfDay; 201 } 202 $dayTranslate = $langs->trans("Day"); 203 if ($iSecond >= ($lengthOfDay * 2)) $dayTranslate = $langs->trans("Days"); 204 } 205 206 if ($lengthOfWeek < 7) 207 { 208 if ($sDay) 209 { 210 if ($sDay >= $lengthOfWeek) 211 { 212 $sWeek = (int) (($sDay - $sDay % $lengthOfWeek) / $lengthOfWeek); 213 $sDay = $sDay % $lengthOfWeek; 214 $weekTranslate = $langs->trans("DurationWeek"); 215 if ($sWeek >= 2) $weekTranslate = $langs->trans("DurationWeeks"); 216 $sTime .= $sWeek.' '.$weekTranslate.' '; 217 } 218 } 219 } 220 if ($sDay > 0) 221 { 222 $dayTranslate = $langs->trans("Day"); 223 if ($sDay > 1) $dayTranslate = $langs->trans("Days"); 224 $sTime .= $sDay.' '.$dayTranslate.' '; 225 } 226 227 if ($format == 'all') 228 { 229 if ($iSecond || empty($sDay)) 230 { 231 $sTime .= dol_print_date($iSecond, 'hourduration', true); 232 } 233 } elseif ($format == 'allhourminsec') 234 { 235 return sprintf("%02d", ($sWeek * $lengthOfWeek * 24 + $sDay * 24 + (int) floor($iSecond / 3600))).':'.sprintf("%02d", ((int) floor(($iSecond % 3600) / 60))).':'.sprintf("%02d", ((int) ($iSecond % 60))); 236 } elseif ($format == 'allhourmin') 237 { 238 return sprintf("%02d", ($sWeek * $lengthOfWeek * 24 + $sDay * 24 + (int) floor($iSecond / 3600))).':'.sprintf("%02d", ((int) floor(($iSecond % 3600) / 60))); 239 } elseif ($format == 'allhour') 240 { 241 return sprintf("%02d", ($sWeek * $lengthOfWeek * 24 + $sDay * 24 + (int) floor($iSecond / 3600))); 242 } 243 } elseif ($format == 'hour') // only hour part 244 { 245 $sTime = dol_print_date($iSecond, '%H', true); 246 } elseif ($format == 'fullhour') 247 { 248 if (!empty($iSecond)) { 249 $iSecond = $iSecond / 3600; 250 } else { 251 $iSecond = 0; 252 } 253 $sTime = $iSecond; 254 } elseif ($format == 'min') // only min part 255 { 256 $sTime = dol_print_date($iSecond, '%M', true); 257 } elseif ($format == 'sec') // only sec part 258 { 259 $sTime = dol_print_date($iSecond, '%S', true); 260 } elseif ($format == 'month') // only month part 261 { 262 $sTime = dol_print_date($iSecond, '%m', true); 263 } elseif ($format == 'year') // only year part 264 { 265 $sTime = dol_print_date($iSecond, '%Y', true); 266 } 267 return trim($sTime); 268} 269 270 271/** 272 * Generate a SQL string to make a filter into a range (for second of date until last second of date) 273 * 274 * @param string $datefield Name of SQL field where apply sql date filter 275 * @param int $day_date Day date 276 * @param int $month_date Month date 277 * @param int $year_date Year date 278 * @param int $excludefirstand Exclude first and 279 * @return string $sqldate String with SQL filter 280 */ 281function dolSqlDateFilter($datefield, $day_date, $month_date, $year_date, $excludefirstand = 0) 282{ 283 global $db; 284 $sqldate = ""; 285 if ($month_date > 0) { 286 if ($year_date > 0 && empty($day_date)) { 287 $sqldate .= ($excludefirstand ? "" : " AND ").$datefield." BETWEEN '".$db->idate(dol_get_first_day($year_date, $month_date, false)); 288 $sqldate .= "' AND '".$db->idate(dol_get_last_day($year_date, $month_date, false))."'"; 289 } elseif ($year_date > 0 && !empty($day_date)) { 290 $sqldate .= ($excludefirstand ? "" : " AND ").$datefield." BETWEEN '".$db->idate(dol_mktime(0, 0, 0, $month_date, $day_date, $year_date)); 291 $sqldate .= "' AND '".$db->idate(dol_mktime(23, 59, 59, $month_date, $day_date, $year_date))."'"; 292 } else $sqldate .= ($excludefirstand ? "" : " AND ")." date_format( ".$datefield.", '%c') = '".$db->escape($month_date)."'"; 293 } elseif ($year_date > 0) { 294 $sqldate .= ($excludefirstand ? "" : " AND ").$datefield." BETWEEN '".$db->idate(dol_get_first_day($year_date, 1, false)); 295 $sqldate .= "' AND '".$db->idate(dol_get_last_day($year_date, 12, false))."'"; 296 } 297 return $sqldate; 298} 299 300/** 301 * Convert a string date into a GM Timestamps date 302 * 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. 303 * 304 * @param string $string Date in a string 305 * YYYYMMDD 306 * YYYYMMDDHHMMSS 307 * YYYYMMDDTHHMMSSZ 308 * YYYY-MM-DDTHH:MM:SSZ (RFC3339) 309 * DD/MM/YY or DD/MM/YYYY (deprecated) 310 * DD/MM/YY HH:MM:SS or DD/MM/YYYY HH:MM:SS (deprecated) 311 * @param int $gm 1 =Input date is GM date, 312 * 0 =Input date is local date using PHP server timezone 313 * @return int Date as a timestamp 314 * 19700101020000 -> 7200 with gm=1 315 * 19700101000000 -> 0 with gm=1 316 * 317 * @see dol_print_date(), dol_mktime(), dol_getdate() 318 */ 319function dol_stringtotime($string, $gm = 1) 320{ 321 $reg = array(); 322 // Convert date with format DD/MM/YYY HH:MM:SS. This part of code should not be used. 323 if (preg_match('/^([0-9]+)\/([0-9]+)\/([0-9]+)\s?([0-9]+)?:?([0-9]+)?:?([0-9]+)?/i', $string, $reg)) 324 { 325 dol_syslog("dol_stringtotime call to function with deprecated parameter format", LOG_WARNING); 326 // Date est au format 'DD/MM/YY' ou 'DD/MM/YY HH:MM:SS' 327 // Date est au format 'DD/MM/YYYY' ou 'DD/MM/YYYY HH:MM:SS' 328 $sday = $reg[1]; 329 $smonth = $reg[2]; 330 $syear = $reg[3]; 331 $shour = $reg[4]; 332 $smin = $reg[5]; 333 $ssec = $reg[6]; 334 if ($syear < 50) $syear += 1900; 335 if ($syear >= 50 && $syear < 100) $syear += 2000; 336 $string = sprintf("%04d%02d%02d%02d%02d%02d", $syear, $smonth, $sday, $shour, $smin, $ssec); 337 } elseif ( 338 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) 339 || 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 340 || 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 341 ) 342 { 343 $syear = $reg[1]; 344 $smonth = $reg[2]; 345 $sday = $reg[3]; 346 $shour = $reg[4]; 347 $smin = $reg[5]; 348 $ssec = $reg[6]; 349 $string = sprintf("%04d%02d%02d%02d%02d%02d", $syear, $smonth, $sday, $shour, $smin, $ssec); 350 } 351 352 $string = preg_replace('/([^0-9])/i', '', $string); 353 $tmp = $string.'000000'; 354 $date = dol_mktime(substr($tmp, 8, 2), substr($tmp, 10, 2), substr($tmp, 12, 2), substr($tmp, 4, 2), substr($tmp, 6, 2), substr($tmp, 0, 4), ($gm ? 1 : 0)); 355 return $date; 356} 357 358 359/** 360 * Return previous day 361 * 362 * @param int $day Day 363 * @param int $month Month 364 * @param int $year Year 365 * @return array Previous year,month,day 366 */ 367function dol_get_prev_day($day, $month, $year) 368{ 369 $time = dol_mktime(12, 0, 0, $month, $day, $year, 1, 0); 370 $time -= 24 * 60 * 60; 371 $tmparray = dol_getdate($time, true); 372 return array('year' => $tmparray['year'], 'month' => $tmparray['mon'], 'day' => $tmparray['mday']); 373} 374 375/** 376 * Return next day 377 * 378 * @param int $day Day 379 * @param int $month Month 380 * @param int $year Year 381 * @return array Next year,month,day 382 */ 383function dol_get_next_day($day, $month, $year) 384{ 385 $time = dol_mktime(12, 0, 0, $month, $day, $year, 1, 0); 386 $time += 24 * 60 * 60; 387 $tmparray = dol_getdate($time, true); 388 return array('year' => $tmparray['year'], 'month' => $tmparray['mon'], 'day' => $tmparray['mday']); 389} 390 391/** 392 * Return previous month 393 * 394 * @param int $month Month 395 * @param int $year Year 396 * @return array Previous year,month 397 */ 398function dol_get_prev_month($month, $year) 399{ 400 if ($month == 1) 401 { 402 $prev_month = 12; 403 $prev_year = $year - 1; 404 } else { 405 $prev_month = $month - 1; 406 $prev_year = $year; 407 } 408 return array('year' => $prev_year, 'month' => $prev_month); 409} 410 411/** 412 * Return next month 413 * 414 * @param int $month Month 415 * @param int $year Year 416 * @return array Next year,month 417 */ 418function dol_get_next_month($month, $year) 419{ 420 if ($month == 12) 421 { 422 $next_month = 1; 423 $next_year = $year + 1; 424 } else { 425 $next_month = $month + 1; 426 $next_year = $year; 427 } 428 return array('year' => $next_year, 'month' => $next_month); 429} 430 431/** 432 * Return previous week 433 * 434 * @param int $day Day 435 * @param int $week Week 436 * @param int $month Month 437 * @param int $year Year 438 * @return array Previous year,month,day 439 */ 440function dol_get_prev_week($day, $week, $month, $year) 441{ 442 $tmparray = dol_get_first_day_week($day, $month, $year); 443 444 $time = dol_mktime(12, 0, 0, $month, $tmparray['first_day'], $year, 1, 0); 445 $time -= 24 * 60 * 60 * 7; 446 $tmparray = dol_getdate($time, true); 447 return array('year' => $tmparray['year'], 'month' => $tmparray['mon'], 'day' => $tmparray['mday']); 448} 449 450/** 451 * Return next week 452 * 453 * @param int $day Day 454 * @param int $week Week 455 * @param int $month Month 456 * @param int $year Year 457 * @return array Next year,month,day 458 */ 459function dol_get_next_week($day, $week, $month, $year) 460{ 461 $tmparray = dol_get_first_day_week($day, $month, $year); 462 463 $time = dol_mktime(12, 0, 0, $tmparray['first_month'], $tmparray['first_day'], $tmparray['first_year'], 1, 0); 464 $time += 24 * 60 * 60 * 7; 465 $tmparray = dol_getdate($time, true); 466 467 return array('year' => $tmparray['year'], 'month' => $tmparray['mon'], 'day' => $tmparray['mday']); 468} 469 470/** 471 * Return GMT time for first day of a month or year 472 * 473 * @param int $year Year 474 * @param int $month Month 475 * @param mixed $gm False or 0 or 'tzserver' = Return date to compare with server TZ, 476 * True or 1 or 'gmt' to compare with GMT date. 477 * 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 478 * 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 479 * @return int Date for first day, '' if error 480 */ 481function dol_get_first_day($year, $month = 1, $gm = false) 482{ 483 if ($year > 9999) return ''; 484 return dol_mktime(0, 0, 0, $month, 1, $year, $gm); 485} 486 487 488/** 489 * Return GMT time for last day of a month or year. 490 * Note: The timestamp contains last day and last hours (23:59:59) 491 * 492 * @param int $year Year 493 * @param int $month Month 494 * @param mixed $gm False or 0 or 'tzserver' = Return date to compare with server TZ, 495 * True or 1 or 'gmt' to compare with GMT date. 496 * @return int Date for first day, '' if error 497 */ 498function dol_get_last_day($year, $month = 12, $gm = false) 499{ 500 if ($year > 9999) return ''; 501 if ($month == 12) 502 { 503 $month = 1; 504 $year += 1; 505 } else { 506 $month += 1; 507 } 508 509 // On se deplace au debut du mois suivant, et on retire un jour 510 $datelim = dol_mktime(23, 59, 59, $month, 1, $year, $gm); 511 $datelim -= (3600 * 24); 512 513 return $datelim; 514} 515 516/** 517 * Return GMT time for last hour of a given GMT date (it removes hours, min and second part) 518 * 519 * @param int $date Date 520 * @param mixed $gm False or 0 or 'tzserver' = Return date to compare with server TZ, 521 * True or 1 or 'gmt' to compare with GMT date. 522 * @return int Date for last hour of a given date 523 */ 524function dol_get_last_hour($date, $gm = 'tzserver') 525{ 526 $tmparray = dol_getdate($date); 527 return dol_mktime(23, 59, 59, $tmparray['mon'], $tmparray['mday'], $tmparray['year'], $gm); 528} 529 530/** 531 * Return GMT time for first hour of a given GMT date (it removes hours, min and second part) 532 * 533 * @param int $date Date 534 * @param mixed $gm False or 0 or 'tzserver' = Return date to compare with server TZ, 535 * True or 1 or 'gmt' to compare with GMT date. 536 * @return int Date for last hour of a given date 537 */ 538function dol_get_first_hour($date, $gm = 'tzserver') 539{ 540 $tmparray = dol_getdate($date); 541 return dol_mktime(0, 0, 0, $tmparray['mon'], $tmparray['mday'], $tmparray['year'], $gm); 542} 543 544/** Return first day of week for a date. First day of week may be monday if option MAIN_START_WEEK is 1. 545 * 546 * @param int $day Day 547 * @param int $month Month 548 * @param int $year Year 549 * @param mixed $gm False or 0 or 'tzserver' = Return date to compare with server TZ, 550 * True or 1 or 'gmt' to compare with GMT date. 551 * @return array year,month,week,first_day,first_month,first_year,prev_day,prev_month,prev_year 552 */ 553function dol_get_first_day_week($day, $month, $year, $gm = false) 554{ 555 global $conf; 556 557 //$day=2; $month=2; $year=2015; 558 $date = dol_mktime(0, 0, 0, $month, $day, $year, $gm); 559 560 //Checking conf of start week 561 $start_week = (isset($conf->global->MAIN_START_WEEK) ? $conf->global->MAIN_START_WEEK : 1); 562 563 $tmparray = dol_getdate($date, true); // detail of current day 564 565 //Calculate days = offset from current day 566 $days = $start_week - $tmparray['wday']; 567 if ($days >= 1) $days = 7 - $days; 568 $days = abs($days); 569 $seconds = $days * 24 * 60 * 60; 570 //print 'start_week='.$start_week.' tmparray[wday]='.$tmparray['wday'].' day offset='.$days.' seconds offset='.$seconds.'<br>'; 571 572 //Get first day of week 573 $tmpdaytms = date($tmparray[0]) - $seconds; // $tmparray[0] is day of parameters 574 $tmpday = date("d", $tmpdaytms); 575 576 //Check first day of week is in same month than current day or not 577 if ($tmpday > $day) 578 { 579 $prev_month = $month - 1; 580 $prev_year = $year; 581 582 if ($prev_month == 0) 583 { 584 $prev_month = 12; 585 $prev_year = $year - 1; 586 } 587 } else { 588 $prev_month = $month; 589 $prev_year = $year; 590 } 591 $tmpmonth = $prev_month; 592 $tmpyear = $prev_year; 593 594 //Get first day of next week 595 $tmptime = dol_mktime(12, 0, 0, $month, $tmpday, $year, 1, 0); 596 $tmptime -= 24 * 60 * 60 * 7; 597 $tmparray = dol_getdate($tmptime, true); 598 $prev_day = $tmparray['mday']; 599 600 //Check prev day of week is in same month than first day or not 601 if ($prev_day > $tmpday) 602 { 603 $prev_month = $month - 1; 604 $prev_year = $year; 605 606 if ($prev_month == 0) 607 { 608 $prev_month = 12; 609 $prev_year = $year - 1; 610 } 611 } 612 613 $week = date("W", dol_mktime(0, 0, 0, $tmpmonth, $tmpday, $tmpyear, $gm)); 614 615 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); 616} 617 618/** 619 * Return the easter day in GMT time. 620 * This function replaces easter_date() that returns a date in local TZ. 621 * 622 * @param int $year Year 623 * @return int GMT Date of easter day 624 */ 625function getGMTEasterDatetime($year) 626{ 627 $base = new DateTime("$year-03-21", new DateTimeZone("UTC")); 628 $days = easter_days($year); // Return number of days between 21 march and easter day. 629 $tmp = $base->add(new DateInterval("P{$days}D")); 630 return $tmp->getTimestamp(); 631} 632 633/** 634 * Return the number of non working days including saturday and sunday (or not) between 2 dates in timestamp. 635 * Dates must be UTC with hour, min, sec to 0. 636 * Called by function num_open_day() 637 * 638 * @param int $timestampStart Timestamp start (UTC with hour, min, sec = 0) 639 * @param int $timestampEnd Timestamp end (UTC with hour, min, sec = 0) 640 * @param string $country_code Country code 641 * @param int $lastday Last day is included, 0: no, 1:yes 642 * @param int $includesaturday Include saturday as non working day (-1=use setup, 0=no, 1=yes) 643 * @param int $includesunday Include sunday as non working day (-1=use setup, 0=no, 1=yes) 644 * @return int|string Number of non working days or error message string if error 645 * @see num_between_day(), num_open_day() 646 */ 647function num_public_holiday($timestampStart, $timestampEnd, $country_code = '', $lastday = 0, $includesaturday = -1, $includesunday = -1) 648{ 649 global $db, $conf, $mysoc; 650 651 $nbFerie = 0; 652 653 // Check to ensure we use correct parameters 654 if ((($timestampEnd - $timestampStart) % 86400) != 0) return 'Error Dates must use same hours and must be GMT dates'; 655 656 if (empty($country_code)) $country_code = $mysoc->country_code; 657 658 if ($includesaturday < 0) $includesaturday = (isset($conf->global->MAIN_NON_WORKING_DAYS_INCLUDE_SATURDAY) ? $conf->global->MAIN_NON_WORKING_DAYS_INCLUDE_SATURDAY : 1); 659 if ($includesunday < 0) $includesunday = (isset($conf->global->MAIN_NON_WORKING_DAYS_INCLUDE_SUNDAY) ? $conf->global->MAIN_NON_WORKING_DAYS_INCLUDE_SUNDAY : 1); 660 661 $country_id = dol_getIdFromCode($db, $country_code, 'c_country', 'code', 'rowid'); 662 663 $i = 0; 664 while ((($lastday == 0 && $timestampStart < $timestampEnd) || ($lastday && $timestampStart <= $timestampEnd)) 665 && ($i < 50000)) // Loop end when equals (Test on i is a security loop to avoid infinite loop) 666 { 667 $ferie = false; 668 $specialdayrule = array(); 669 670 $jour = gmdate("d", $timestampStart); 671 $mois = gmdate("m", $timestampStart); 672 $annee = gmdate("Y", $timestampStart); 673 674 //print "jour=".$jour." month=".$mois." year=".$annee." includesaturday=".$includesaturday." includesunday=".$includesunday."\n"; 675 676 // Loop on public holiday defined into hrm_public_holiday for the day, month and year analyzed 677 // TODO Execute this request first and store results into an array, then reuse this array. 678 $sql = "SELECT code, entity, fk_country, dayrule, year, month, day, active"; 679 $sql .= " FROM ".MAIN_DB_PREFIX."c_hrm_public_holiday"; 680 $sql .= " WHERE active = 1 and fk_country IN (0".($country_id > 0 ? ", ".$country_id : 0).")"; 681 682 $resql = $db->query($sql); 683 if ($resql) 684 { 685 $num_rows = $db->num_rows($resql); 686 $i = 0; 687 while ($i < $num_rows) 688 { 689 $obj = $db->fetch_object($resql); 690 691 if (!empty($obj->dayrule) && $obj->dayrule != 'date') // For example 'easter', '...' 692 { 693 $specialdayrule[$obj->dayrule] = $obj->dayrule; 694 } else { 695 $match = 1; 696 if (!empty($obj->year) && $obj->year != $annee) $match = 0; 697 if ($obj->month != $mois) $match = 0; 698 if ($obj->day != $jour) $match = 0; 699 700 if ($match) $ferie = true; 701 } 702 703 $i++; 704 } 705 } else { 706 dol_syslog($db->lasterror(), LOG_ERR); 707 return 'Error sql '.$db->lasterror(); 708 } 709 //var_dump($specialdayrule)."\n"; 710 //print "ferie=".$ferie."\n"; 711 712 if (!$ferie) { 713 // Special dayrules 714 if (in_array('easter', $specialdayrule)) 715 { 716 // Calculation for easter date 717 $date_paques = getGMTEasterDatetime($annee); 718 $jour_paques = gmdate("d", $date_paques); 719 $mois_paques = gmdate("m", $date_paques); 720 if ($jour_paques == $jour && $mois_paques == $mois) $ferie = true; 721 // Easter (sunday) 722 } 723 724 if (in_array('eastermonday', $specialdayrule)) 725 { 726 // Calculation for the monday of easter date 727 $date_paques = getGMTEasterDatetime($annee); 728 //print 'PPP'.$date_paques.' '.dol_print_date($date_paques, 'dayhour', 'gmt')." "; 729 $date_lundi_paques = $date_paques + (3600 * 24); 730 $jour_lundi_paques = gmdate("d", $date_lundi_paques); 731 $mois_lundi_paques = gmdate("m", $date_lundi_paques); 732 if ($jour_lundi_paques == $jour && $mois_lundi_paques == $mois) $ferie = true; 733 // Easter (monday) 734 //print 'annee='.$annee.' $jour='.$jour.' $mois='.$mois.' $jour_lundi_paques='.$jour_lundi_paques.' $mois_lundi_paques='.$mois_lundi_paques."\n"; 735 } 736 737 if (in_array('ascension', $specialdayrule)) 738 { 739 // Calcul du jour de l'ascension (39 days after easter day) 740 $date_paques = getGMTEasterDatetime($annee); 741 $date_ascension = $date_paques + (3600 * 24 * 39); 742 $jour_ascension = gmdate("d", $date_ascension); 743 $mois_ascension = gmdate("m", $date_ascension); 744 if ($jour_ascension == $jour && $mois_ascension == $mois) $ferie = true; 745 // Ascension (thursday) 746 } 747 748 if (in_array('pentecote', $specialdayrule)) 749 { 750 // Calculation of "Pentecote" (49 days after easter day) 751 $date_paques = getGMTEasterDatetime($annee); 752 $date_pentecote = $date_paques + (3600 * 24 * 49); 753 $jour_pentecote = gmdate("d", $date_pentecote); 754 $mois_pentecote = gmdate("m", $date_pentecote); 755 if ($jour_pentecote == $jour && $mois_pentecote == $mois) $ferie = true; 756 // "Pentecote" (sunday) 757 } 758 if (in_array('pentecotemonday', $specialdayrule)) 759 { 760 // Calculation of "Pentecote" (49 days after easter day) 761 $date_paques = getGMTEasterDatetime($annee); 762 $date_pentecote = $date_paques + (3600 * 24 * 50); 763 $jour_pentecote = gmdate("d", $date_pentecote); 764 $mois_pentecote = gmdate("m", $date_pentecote); 765 if ($jour_pentecote == $jour && $mois_pentecote == $mois) $ferie = true; 766 // "Pentecote" (monday) 767 } 768 769 if (in_array('viernessanto', $specialdayrule)) 770 { 771 // Viernes Santo 772 $date_paques = getGMTEasterDatetime($annee); 773 $date_viernes = $date_paques - (3600 * 24 * 2); 774 $jour_viernes = gmdate("d", $date_viernes); 775 $mois_viernes = gmdate("m", $date_viernes); 776 if ($jour_viernes == $jour && $mois_viernes == $mois) $ferie = true; 777 //Viernes Santo 778 } 779 780 if (in_array('fronleichnam', $specialdayrule)) 781 { 782 // Fronleichnam (60 days after easter sunday) 783 $date_paques = getGMTEasterDatetime($annee); 784 $date_fronleichnam = $date_paques + (3600 * 24 * 60); 785 $jour_fronleichnam = gmdate("d", $date_fronleichnam); 786 $mois_fronleichnam = gmdate("m", $date_fronleichnam); 787 if ($jour_fronleichnam == $jour && $mois_fronleichnam == $mois) $ferie = true; 788 // Fronleichnam 789 } 790 } 791 //print "ferie=".$ferie."\n"; 792 793 // If we have to include saturday and sunday 794 if (!$ferie) { 795 if ($includesaturday || $includesunday) 796 { 797 $jour_julien = unixtojd($timestampStart); 798 $jour_semaine = jddayofweek($jour_julien, 0); 799 if ($includesaturday) //Saturday (6) and Sunday (0) 800 { 801 if ($jour_semaine == 6) $ferie = true; 802 } 803 if ($includesunday) //Saturday (6) and Sunday (0) 804 { 805 if ($jour_semaine == 0) $ferie = true; 806 } 807 } 808 } 809 //print "ferie=".$ferie."\n"; 810 811 // We increase the counter of non working day 812 if ($ferie) $nbFerie++; 813 814 // Increase number of days (on go up into loop) 815 $timestampStart = dol_time_plus_duree($timestampStart, 1, 'd'); 816 //var_dump($jour.' '.$mois.' '.$annee.' '.$timestampStart); 817 818 $i++; 819 } 820 821 //print "nbFerie=".$nbFerie."\n"; 822 return $nbFerie; 823} 824 825/** 826 * Function to return number of days between two dates (date must be UTC date !) 827 * Example: 2012-01-01 2012-01-02 => 1 if lastday=0, 2 if lastday=1 828 * 829 * @param int $timestampStart Timestamp start UTC 830 * @param int $timestampEnd Timestamp end UTC 831 * @param int $lastday Last day is included, 0: no, 1:yes 832 * @return int Number of days 833 * @seealso num_public_holiday(), num_open_day() 834 */ 835function num_between_day($timestampStart, $timestampEnd, $lastday = 0) 836{ 837 if ($timestampStart < $timestampEnd) 838 { 839 if ($lastday == 1) 840 { 841 $bit = 0; 842 } else { 843 $bit = 1; 844 } 845 $nbjours = (int) floor(($timestampEnd - $timestampStart) / (60 * 60 * 24)) + 1 - $bit; 846 } 847 //print ($timestampEnd - $timestampStart) - $lastday; 848 return $nbjours; 849} 850 851/** 852 * Function to return number of working days (and text of units) between two dates (working days) 853 * 854 * @param int $timestampStart Timestamp for start date (date must be UTC to avoid calculation errors) 855 * @param int $timestampEnd Timestamp for end date (date must be UTC to avoid calculation errors) 856 * @param int $inhour 0: return number of days, 1: return number of hours 857 * @param int $lastday We include last day, 0: no, 1:yes 858 * @param int $halfday Tag to define half day when holiday start and end 859 * @param string $country_code Country code (company country code if not defined) 860 * @return int|string Number of days or hours or string if error 861 * @seealso num_between_day(), num_public_holiday() 862 */ 863function num_open_day($timestampStart, $timestampEnd, $inhour = 0, $lastday = 0, $halfday = 0, $country_code = '') 864{ 865 global $langs, $mysoc; 866 867 if (empty($country_code)) $country_code = $mysoc->country_code; 868 869 dol_syslog('num_open_day timestampStart='.$timestampStart.' timestampEnd='.$timestampEnd.' bit='.$lastday.' country_code='.$country_code); 870 871 // Check parameters 872 if (!is_int($timestampStart) && !is_float($timestampStart)) return 'ErrorBadParameter_num_open_day'; 873 if (!is_int($timestampEnd) && !is_float($timestampEnd)) return 'ErrorBadParameter_num_open_day'; 874 875 //print 'num_open_day timestampStart='.$timestampStart.' timestampEnd='.$timestampEnd.' bit='.$lastday; 876 if ($timestampStart < $timestampEnd) 877 { 878 $numdays = num_between_day($timestampStart, $timestampEnd, $lastday); 879 880 $numholidays = num_public_holiday($timestampStart, $timestampEnd, $country_code, $lastday); 881 $nbOpenDay = ($numdays - $numholidays); 882 if ($inhour == 1 && $nbOpenDay <= 3) $nbOpenDay = ($nbOpenDay * 24); 883 return $nbOpenDay - (($inhour == 1 ? 12 : 0.5) * abs($halfday)); 884 } elseif ($timestampStart == $timestampEnd) 885 { 886 $numholidays = 0; 887 if ($lastday) { 888 $numholidays = num_public_holiday($timestampStart, $timestampEnd, $country_code, $lastday); 889 if ($numholidays == 1) return 0; 890 } 891 892 $nbOpenDay = $lastday; 893 894 if ($inhour == 1) $nbOpenDay = ($nbOpenDay * 24); 895 return $nbOpenDay - (($inhour == 1 ? 12 : 0.5) * abs($halfday)); 896 } else { 897 return $langs->trans("Error"); 898 } 899} 900 901 902 903/** 904 * Return array of translated months or selected month. 905 * This replace old function monthArrayOrSelected. 906 * 907 * @param Translate $outputlangs Object langs 908 * @param int $short 0=Return long label, 1=Return short label 909 * @return array Month string or array if selected < 0 910 */ 911function monthArray($outputlangs, $short = 0) 912{ 913 $montharray = array( 914 1 => $outputlangs->trans("Month01"), 915 2 => $outputlangs->trans("Month02"), 916 3 => $outputlangs->trans("Month03"), 917 4 => $outputlangs->trans("Month04"), 918 5 => $outputlangs->trans("Month05"), 919 6 => $outputlangs->trans("Month06"), 920 7 => $outputlangs->trans("Month07"), 921 8 => $outputlangs->trans("Month08"), 922 9 => $outputlangs->trans("Month09"), 923 10 => $outputlangs->trans("Month10"), 924 11 => $outputlangs->trans("Month11"), 925 12 => $outputlangs->trans("Month12") 926 ); 927 928 if (!empty($short)) 929 { 930 $montharray = array( 931 1 => $outputlangs->trans("MonthShort01"), 932 2 => $outputlangs->trans("MonthShort02"), 933 3 => $outputlangs->trans("MonthShort03"), 934 4 => $outputlangs->trans("MonthShort04"), 935 5 => $outputlangs->trans("MonthShort05"), 936 6 => $outputlangs->trans("MonthShort06"), 937 7 => $outputlangs->trans("MonthShort07"), 938 8 => $outputlangs->trans("MonthShort08"), 939 9 => $outputlangs->trans("MonthShort09"), 940 10 => $outputlangs->trans("MonthShort10"), 941 11 => $outputlangs->trans("MonthShort11"), 942 12 => $outputlangs->trans("MonthShort12") 943 ); 944 } 945 946 return $montharray; 947} 948/** 949 * Return array of week numbers. 950 * 951 * @param int $month Month number 952 * @param int $year Year number 953 * @return array Week numbers 954 */ 955function getWeekNumbersOfMonth($month, $year) 956{ 957 $nb_days = cal_days_in_month(CAL_GREGORIAN, $month, $year); 958 $TWeek = array(); 959 for ($day = 1; $day < $nb_days; $day++) { 960 $week_number = getWeekNumber($day, $month, $year); 961 $TWeek[$week_number] = $week_number; 962 } 963 return $TWeek; 964} 965/** 966 * Return array of first day of weeks. 967 * 968 * @param array $TWeek array of week numbers 969 * @param int $year Year number 970 * @return array First day of week 971 */ 972function getFirstDayOfEachWeek($TWeek, $year) 973{ 974 $TFirstDayOfWeek = array(); 975 foreach ($TWeek as $weekNb) { 976 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 977 $TFirstDayOfWeek[$weekNb] = date('d', strtotime($year.'W'.$weekNb)); 978 } 979 return $TFirstDayOfWeek; 980} 981/** 982 * Return array of last day of weeks. 983 * 984 * @param array $TWeek array of week numbers 985 * @param int $year Year number 986 * @return array Last day of week 987 */ 988function getLastDayOfEachWeek($TWeek, $year) 989{ 990 $TLastDayOfWeek = array(); 991 foreach ($TWeek as $weekNb) { 992 $TLastDayOfWeek[$weekNb] = date('d', strtotime($year.'W'.$weekNb.'+6 days')); 993 } 994 return $TLastDayOfWeek; 995} 996/** 997 * Return week number. 998 * 999 * @param int $day Day number 1000 * @param int $month Month number 1001 * @param int $year Year number 1002 * @return int Week number 1003 */ 1004function getWeekNumber($day, $month, $year) 1005{ 1006 $date = new DateTime($year.'-'.$month.'-'.$day); 1007 $week = $date->format("W"); 1008 return $week; 1009} 1010