Zitat:
Zitat von zeusel Hallo Ihr,
eine Frage: ich will in einem Script 10 Werktage zu einem bestimmten Datum hinzuaddieren ...
... nur sind derzeit ja auch die Samstage und Sonntage mit inbegriffen. |
*korinthenkack*
Der Samstag ist ein Werktag. Damit wird die "weekday"-Rechnung nicht funktionieren. Die kennt nur die im englischsprachigen Raum üblichen Arbeitstage ("working days" oder "business days") und hat von
deutschen (bundesweiten und landesspezifischen) Feiertagsregelungen nicht die leiseste Ahnung.
Zitat:
|
Kann man diese Funktion erweitern, dass das hinzurechnen nur auf die Wochentage erfolgt? Gibt es da vielleicht auch eine Funktion?
|
Es gibt fertige Tabellen für jedes Jahr, und Tools die solche Fristen berechnen können, bspw.:
http://www.datumsrechner.de/
Eine fertige Funktion dafür hat PHP bisher nicht.
Java schon. Die
Berechnungsbeispiele zeigen, dass die Sache nicht so trivial ist, wie man sich das gemeinhin vorstellt.
Damit das nicht zu negativ klingt, hier noch etwas Konstruktives: Ich missbrauche preg_match() als Werktagszähler. Was Arbeitstage sind, ist weitesgehend frei einstellbar und die Berechnung funktioniert auch über Jahres-Grenzen hinweg.
Wer die Klasse um eigene oder landesspezifische Feiertage erweitern möchte, überschreibt in einer vererbten Variante einfach die Funktion ::holidays().
Die etwas umständlichen Datums-Format-Wandlungen sind der Tatsache geschuldet, dass ich (bis auf die Schaltjahres-Berechnung) nur die in PHP eingebauten Datumsfunktionen benutzt habe. Die ursprüngliche Variante, die nur mit Jahr und Tagesnummer rechnet, kam ohne Umwandlungen aus. Dafür sind Ein- und Ausgabeformat jetzt flexibler.
PHP-Code:
class simple_day_skipper {
/// basic date calculations using mkdate(), strtotime(), idate()
/// calculate day of the week for the given year-month-day
/// return int(0...6) 0 MON ... 6 SAT
static function day_of_the_week(
$y,
$m,
$d = 1
) {
return is_int($dotw = idate('w', mktime(0, 0, 0, $m, $d, $y)))
? $dotw : null;
}
/// convert given string to year + daynum (1...3666)
/// return array($year, $daynum) | null()
static function daynum(
$date /// str() see strtotime() for accepted formats
) {
if (!is_int($tstamp = strtotime($date))) {
return null;
}
return array (
idate('Y', $tstamp),
idate('z', $tstamp) + 1
// the daynum from idate() seems to be 0-based
);
}
static function leap_year($year) {
return ($year & 3 || (0 !== $year % 400) && (0 === $year % 100))
? 0 : 1;
}
/// create a list of holidays
/// return list() of int(1...366)
static function holidays($year) {
$leap = self::leap_year($year);
// easter_days() returns number of days after MAR/21st (dunno why)
$easter = easter_days($year) + 81 + $leap;
return array (
// (DE) bundesweite Feiertage
// on fixed day
1, // JAN/1st
121 + $leap, // MAY/1st
276 + $leap, // OCT/3rd
359 + $leap, // DEC/25th
360 + $leap, // DEC/26th
// relative to easter sunday
$easter - 2, // easter: FRI
$easter, // easter: SUN
$easter + 1, // easter: MON
$easter + 49, // pentecost: SUN
$easter + 50, // pentecost: MON
);
}
/// create a year as a sequence of bytes
/// return str(binary) year as sequence of bytes
static function year_as_binary(
$year /// int()
) {
$days_in_year = 365 + self::leap_year($year);
if (!is_int($dotw_jan_1st = self::day_of_the_week($year, 1, 1))) {
return null;
}
$tmpl = str_repeat("\x00\x01\x02\x03\x04\x05\x06", 55);
$y = substr($tmpl, $dotw_jan_1st, $days_in_year);
// mark holidays
foreach (self::holidays($year) as $daynum) {
$daynum -= 1; // daynum is 1-based, string offsets are 0-based
$y[$daynum] = chr(ord($y[$daynum]) - 7);
}
return $y;
}
/// the simple_day_skipper object
/// constructor
/// return -
function __construct() {
$this->fmt_output = null;
$this->format();
$this->pcre = null;
$this->working_days();
return;
}
/// set range of working days (invalid values get ignored)
/// return $this
function working_days(
$a = 1, /// int(0...6) defaults to 1 (MON)
$b = 5 /// int(0...6) defaults to 5 (FRI)
) {
foreach ($args = array ($a, $b) as $val) {
if (!is_int($val) || $val < 0 || $val > 6) {
return $this;
}
}
sort($args); // need the lower one 1st
$this->pcre = vsprintf('\x%02x-\x%02x', $args);
//return sscanf($this->pcre, '\x%d-\x%d');
return $this;
}
/// set output-format. invalid values get ignored.
/// return $this
function format(
$fmt = null // see strfttime() for allowed format templates
) {
$this->fmt_output = is_string(strftime($fmt))
? $fmt
: '%Y-%m-%d'; // ISO date format
return $this;
}
/// skip the given amount of working days and get the target date
/// return str() strftime()-formatted target date
function skip_days(
$basedate, /// str() see strtotime() for allowed formats
$daydiff /// int(1...) number of working-days to skip
) {
if (!is_array(list ($year, $daynum) = $this->daynum($basedate))) {
return null;
}
while (is_string($y = self::year_as_binary($year))) {
preg_match(
// {0,%d} is for years which end with non-working-days
sprintf(
'/(?:[^%s]*[%s]|[^%s]*\z){0,%d}/',
$this->pcre, $this->pcre, $this->pcre, $daydiff
),
$y, $h, null, $daynum
// string offsets are 0-based,
// but we start counting at the next day
);
if ($daynum + strlen($h[0]) < strlen($y)) {
break;
}
// end of current year reached
$daydiff -= strlen(
preg_replace(sprintf('/[^%s]+/', $this->pcre), '', $h[0])
);
if (0 === $daydiff) {
break;
}
$daynum = 1; // 1st day in next year
++$year;
//break;
}
return is_int($tstamp = strtotime(
// PostGreSQL format YYYY.DDD (day is 1-based)
sprintf('%04d.%03d', $year, $daynum + strlen($h[0]))
))
? strftime($this->fmt_output, $tstamp)
: null;
}
}
Ein paar Tests zeigen die Anwendung:
PHP-Code:
$tests = array (
array ('2011-12-27', 2),
array ('2011-12-28', 2),
array ('2011-12-29', 2),
array ('2011-12-30', 2),
array ('2011-12-31', 2),
array ('2012-01-01', 2),
array ('2012-01-02', 2),
);
$skippy = new simple_day_skipper();
// Ausgabeformat des Zieldatums festlegen:
$skippy->format('%Y %m %d');
// Wir zaehlen Arbeitstage, also Montag (1) bis Freitag (5)
$skippy->working_days(1, 5);
foreach ($tests as $test) {
list ($base_date, $days_to_skip) = $test;
$target_date = $skippy->skip_days($base_date, $days_to_skip);
printf("%s + %s => %s\r\n", $base_date, $days_to_skip, $target_date);
}
// das Gleiche mit Werktagen: Montag bis Samstag (6)
$skippy->working_days(1,6);
foreach ($tests as $test) {
list ($base_date, $days_to_skip) = $test;
$target_date = $skippy->skip_days($base_date, $days_to_skip);
printf("%s + %s => %s\r\n", $base_date, $days_to_skip, $target_date);
}