PHP DateTime :: modyfikacja dodawania i odejmowania miesięcy


102

Dużo pracowałem z DateTime classi ostatnio napotkałem coś, co uważałem za błąd podczas dodawania miesięcy. Po krótkich badaniach okazuje się, że nie był to błąd, ale działał zgodnie z przeznaczeniem. Zgodnie z dokumentacją znajdującą się tutaj :

Przykład # 2 Uważaj podczas dodawania lub odejmowania miesięcy

<?php
$date = new DateTime('2000-12-31');

$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";

$date->modify('+1 month');
echo $date->format('Y-m-d') . "\n";
?>
The above example will output:
2001-01-31
2001-03-03

Czy ktoś może uzasadnić, dlaczego nie jest to uważane za błąd?

Co więcej, czy ktoś ma jakieś eleganckie rozwiązania, aby rozwiązać problem i sprawić, by +1 miesiąc działał zgodnie z oczekiwaniami, a nie zgodnie z przeznaczeniem?


Czego można się spodziewać "2001-01-31" plus 1 miesiąc? ... "2001-02-28"? „2001-03-01”?
Artefacto

57
Osobiście spodziewałbym się, że będzie to 2001-02-28.
tplaner


2
Tak, to dość denerwujące dziwactwo. Przeczytałeś drobny druk, aby dowiedzieć się, że P1M to 31 dni. Naprawdę nie rozumiem, dlaczego ludzie wciąż bronią tego jako „właściwego” zachowania.
Indivision Dev,

Wydaje się, że popularna opinia jest taka, że ​​logika powinna zaokrąglić w dół (do 2/28), chociaż PHP zaokrągla w górę (do 3/1) ... chociaż wolę sposób PHP, ale Microsoft Excel zaokrągla w dół, stawiając twórców stron internetowych przeciwko użytkownikom arkuszy kalkulacyjnych ...
Dave Heq,

Odpowiedzi:


107

Dlaczego to nie jest błąd:

Obecne zachowanie jest poprawne. Następujące rzeczy mają miejsce wewnętrznie:

  1. +1 monthzwiększa numer miesiąca (pierwotnie 1) o jeden. To tworzy datę 2010-02-31.

  2. Drugi miesiąc (luty) ma tylko 28 dni w 2010 roku, więc PHP automatycznie koryguje to, kontynuując liczenie dni od 1 lutego. Następnie kończysz 3 marca.

Jak zdobyć to, czego chcesz:

Aby uzyskać to, czego chcesz, musisz: ręcznie sprawdzić następny miesiąc. Następnie dodaj liczbę dni, które ma następny miesiąc.

Mam nadzieję, że możesz sam to zakodować. Po prostu daję co robić.

Sposób PHP 5.3:

Aby uzyskać poprawne zachowanie, możesz użyć jednej z nowych funkcji PHP 5.3, która wprowadza sekcję czasu względnego first day of. Ta strofa może być stosowany w połączeniu z next month, fifth monthlub +8 monthsudać się do pierwszego dnia danego miesiąca. Zamiast +1 monthtego, co robisz, możesz użyć tego kodu, aby uzyskać pierwszy dzień następnego miesiąca w następujący sposób:

<?php
$d = new DateTime( '2010-01-31' );
$d->modify( 'first day of next month' );
echo $d->format( 'F' ), "\n";
?>

Ten skrypt poprawnie wypisze February. Następujące rzeczy mają miejsce, gdy PHP przetwarza tę first day of next monthsekcję:

  1. next monthzwiększa numer miesiąca (pierwotnie 1) o jeden. To sprawia, że ​​data 2010-02-31.

  2. first day ofustawia numer dnia na 1, co daje datę 2010-02-01.


1
Więc mówisz, że dosłownie dodaje 1 miesiąc, całkowicie ignorując dni? Więc zakładam, że możesz napotkać podobny problem z +1 rokiem, jeśli dodasz go w roku przestępnym?
tplaner

@evolve, tak, literacki dodaje 1 miesiąc.
shamittomar

13
Zakładam, że jeśli odejmiemy 1 miesiąc po dodaniu, otrzymacie zupełnie inną datę. Wydaje się to bardzo nieintuicyjne.
Dan Breen

2
Świetny przykład użycia nowych zwrotek w PHP 5.3, w którym możesz użyć pierwszego dnia, ostatniego dnia, tego miesiąca, następnego miesiąca i poprzedniego miesiąca.
Kim Stacks

7
imho to jest błąd. poważny błąd. jeśli chcę dodać 31 dni, dodaję 31 dni. Chcę dodać miesiąc, należy dodać miesiąc, a nie 31 dni.
low_rents

12

Oto kolejne kompaktowe rozwiązanie całkowicie wykorzystujące metody DateTime, modyfikujące obiekt w miejscu bez tworzenia klonów.

$dt = new DateTime('2012-01-31');

echo $dt->format('Y-m-d'), PHP_EOL;

$day = $dt->format('j');
$dt->modify('first day of +1 month');
$dt->modify('+' . (min($day, $dt->format('t')) - 1) . ' days');

echo $dt->format('Y-m-d'), PHP_EOL;

Wyprowadza:

2012-01-31
2012-02-29

1
Dzięki. Jak dotąd najlepsze rozwiązanie tutaj. Możesz również skrócić kod do $dt->modify()->modify(). Działa równie dobrze.
Alph. Dev

10

Może to być przydatne:

echo Date("Y-m-d", strtotime("2013-01-01 +1 Month -1 Day"));
  // 2013-01-31

echo Date("Y-m-d", strtotime("2013-02-01 +1 Month -1 Day"));
  // 2013-02-28

echo Date("Y-m-d", strtotime("2013-03-01 +1 Month -1 Day"));
  // 2013-03-31

echo Date("Y-m-d", strtotime("2013-04-01 +1 Month -1 Day"));
  // 2013-04-30

echo Date("Y-m-d", strtotime("2013-05-01 +1 Month -1 Day"));
  // 2013-05-31

echo Date("Y-m-d", strtotime("2013-06-01 +1 Month -1 Day"));
  // 2013-06-30

echo Date("Y-m-d", strtotime("2013-07-01 +1 Month -1 Day"));
  // 2013-07-31

echo Date("Y-m-d", strtotime("2013-08-01 +1 Month -1 Day"));
  // 2013-08-31

echo Date("Y-m-d", strtotime("2013-09-01 +1 Month -1 Day"));
  // 2013-09-30

echo Date("Y-m-d", strtotime("2013-10-01 +1 Month -1 Day"));
  // 2013-10-31

echo Date("Y-m-d", strtotime("2013-11-01 +1 Month -1 Day"));
  // 2013-11-30

echo Date("Y-m-d", strtotime("2013-12-01 +1 Month -1 Day"));
  // 2013-12-31

2
Nie jest to ogólne rozwiązanie, ponieważ działa tylko w przypadku niektórych danych wejściowych, takich jak pierwszy dzień miesiąca. Np. Robienie tego 30 stycznia prowadzi do cierpienia.
Jens Roland

Albo możesz to zrobić$dateTime->modify('first day of next month')->modify('-1day')
Anthony

6

Moje rozwiązanie problemu:

$startDate = new \DateTime( '2015-08-30' );
$endDate = clone $startDate;

$billing_count = '6';
$billing_unit = 'm';

$endDate->add( new \DateInterval( 'P' . $billing_count . strtoupper( $billing_unit ) ) );

if ( intval( $endDate->format( 'n' ) ) > ( intval( $startDate->format( 'n' ) ) + intval( $billing_count ) ) % 12 )
{
    if ( intval( $startDate->format( 'n' ) ) + intval( $billing_count ) != 12 )
    {
        $endDate->modify( 'last day of -1 month' );
    }
}

3
Polecenie „klonuj” było rozwiązaniem moich problemów z przypisywaniem zmiennych. Dziękuję Ci za to.
Steph Rose

4

Zgadzam się z opinią OP, że jest to sprzeczne z intuicją i frustrujące, ale tak samo jest z określeniem, co +1 monthoznacza w scenariuszach, w których tak się dzieje. Rozważ te przykłady:

Rozpoczynasz od 31.01.2015 i chcesz dodać miesiąc 6 razy, aby uzyskać cykl planowania wysyłania biuletynu e-mail. Biorąc pod uwagę początkowe oczekiwania PO, powróci to:

  • 2015-01-31
  • 2015-02-28
  • 2015-03-31
  • 2015-04-30
  • 2015-05-31
  • 2015-06-30

Od razu zauważ, że spodziewamy się, że będziemy +1 monthoznaczać last day of monthlub, alternatywnie, dodawać 1 miesiąc na iterację, ale zawsze w odniesieniu do punktu początkowego. Zamiast interpretować to jako „ostatni dzień miesiąca”, możemy go odczytać jako „31 dzień następnego miesiąca lub ostatni dostępny w tym miesiącu”. Oznacza to, że skaczemy od 30 kwietnia do 31 maja zamiast do 30 maja. Zauważ, że nie dzieje się tak dlatego, że jest to „ostatni dzień miesiąca”, ale dlatego, że chcemy mieć „najbliższą dostępną datę miesiąca początkowego”.

Załóżmy więc, że jeden z naszych użytkowników subskrybuje inny biuletyn, który rozpocznie się 30.01.2015. Po co jest intuicyjna data +1 month? Jedna interpretacja to „30 dzień następnego miesiąca lub najbliższy dostępny”, co zwróci:

  • 2015-01-30
  • 2015-02-28
  • 2015-03-30
  • 2015-04-30
  • 2015-05-30
  • 2015-06-30

Byłoby to w porządku, z wyjątkiem sytuacji, gdy nasz użytkownik otrzyma oba biuletyny tego samego dnia. Załóżmy, że jest to problem po stronie podaży, a nie po stronie popytu Nie martwimy się, że użytkownik będzie zirytowany otrzymaniem 2 biuletynów tego samego dnia, ale zamiast tego nasze serwery pocztowe nie mogą pozwolić sobie na przepustowość do wysyłania dwa razy więcej wiele biuletynów. Mając to na uwadze, wrócimy do innej interpretacji „+1 miesiąc” jako „wyślij przedostatniego dnia każdego miesiąca”, która zwróci:

  • 2015-01-30
  • 2015-02-27
  • 2015-03-30
  • 2015-04-29
  • 2015-05-30
  • 2015-06-29

Teraz uniknęliśmy jakiegokolwiek nakładania się pierwszego zestawu, ale kończymy również na 29 kwietnia i czerwca, co z pewnością pasuje do naszych pierwotnych intuicji, które +1 monthpo prostu powinny powrócić m/$d/Ylub są atrakcyjne i proste m/30/Yna wszystkie możliwe miesiące. Rozważmy teraz trzecią interpretację +1 monthużycia obu dat:

31 stycznia

  • 2015-01-31
  • 2015-03-03
  • 2015-03-31
  • 2015-05-01
  • 2015-05-31
  • 2015-07-01

30 stycznia

  • 2015-01-30
  • 2015-03-02
  • 2015-03-30
  • 2015-04-30
  • 2015-05-30
  • 2015-06-30

Powyższe ma pewne problemy. Luty jest pomijany, co może stanowić problem zarówno pod koniec podaży (powiedzmy, że miesięczny przydział przepustowości, a luty się marnuje, a marzec się podwoi) i popyt (użytkownicy czują się oszukani po lutym i dostrzegają dodatkowy marzec jako próba naprawienia błędu). Z drugiej strony zwróć uwagę, że te dwa zestawy dat:

  • nigdy się nie nakładają
  • są zawsze w tym samym dniu, w którym data ma ten miesiąc (więc zestaw z 30 stycznia wygląda całkiem czysto)
  • są w ciągu 3 dni (w większości przypadków 1) od daty, którą można uznać za „prawidłową”.
  • wszystkie są co najmniej 28 dni (miesiąc księżycowy) od swojego następcy i poprzednika, więc są bardzo równomiernie rozłożone.

Biorąc pod uwagę ostatnie dwa zestawy, nie byłoby trudno po prostu cofnąć jedną z dat, jeśli przypada ona poza faktyczny następny miesiąc (więc cofnij się do 28 lutego i 30 kwietnia w pierwszym zestawie) i nie stracić snu w ciągu sporadyczne nakładanie się i rozbieżność między wzorcem „ostatni dzień miesiąca” a „od drugiego do ostatniego dnia miesiąca”. Ale oczekiwanie, że biblioteka wybierze między „najpiękniejszą / naturalną”, „matematyczną interpretacją danych z 31 lutego i innych przepełnień miesiąca” oraz „w stosunku do pierwszego lub ostatniego miesiąca” zawsze kończy się niespełnieniem czyichś oczekiwań i jakiś harmonogram wymaga dostosowania „złej” daty, aby uniknąć rzeczywistego problemu, który wprowadza „zła” interpretacja.

Więc znowu, chociaż spodziewałbym +1 monthsię zwrócić datę, która faktycznie przypada w następnym miesiącu, nie jest to tak proste, jak intuicja, a biorąc pod uwagę możliwości, przejście z matematyką ponad oczekiwania twórców stron internetowych jest prawdopodobnie bezpiecznym wyborem.

Oto alternatywne rozwiązanie, które nadal jest tak samo niezgrabne, jak inne, ale myślę, że ma dobre wyniki:

foreach(range(0,5) as $count) {
    $new_date = clone $date;
    $new_date->modify("+$count month");
    $expected_month = $count + 1;
    $actual_month = $new_date->format("m");
    if($expected_month != $actual_month) {
        $new_date = clone $date;
        $new_date->modify("+". ($count - 1) . " month");
        $new_date->modify("+4 weeks");
    }
    
    echo "* " . nl2br($new_date->format("Y-m-d") . PHP_EOL);
}

Nie jest to optymalne, ale podstawowa logika jest taka: jeśli dodanie 1 miesiąca skutkuje datą inną niż oczekiwany następny miesiąc, wyrzuć tę datę i zamiast tego dodaj 4 tygodnie. Oto wyniki z dwoma datami testu:

31 stycznia

  • 2015-01-31
  • 2015-02-28
  • 2015-03-31
  • 2015-04-28
  • 2015-05-31
  • 2015-06-28

30 stycznia

  • 2015-01-30
  • 2015-02-27
  • 2015-03-30
  • 2015-04-30
  • 2015-05-30
  • 2015-06-30

(Mój kod jest bałaganem i nie działałby w scenariuszu wieloletnim. Zapraszam każdego do przepisania rozwiązania z bardziej eleganckim kodem, o ile podstawowa przesłanka pozostanie nienaruszona, tj. Jeśli +1 miesiąc zwraca funkową datę, użyj +4 tygodnie zamiast tego.)


4

Stworzyłem funkcję, która zwraca DateInterval, aby upewnić się, że dodanie miesiąca pokazuje następny miesiąc i usuwa kolejne dni.

$time = new DateTime('2014-01-31');
echo $time->format('d-m-Y H:i') . '<br/>';

$time->add( add_months(1, $time));

echo $time->format('d-m-Y H:i') . '<br/>';



function add_months( $months, \DateTime $object ) {
    $next = new DateTime($object->format('d-m-Y H:i:s'));
    $next->modify('last day of +'.$months.' month');

    if( $object->format('d') > $next->format('d') ) {
        return $object->diff($next);
    } else {
        return new DateInterval('P'.$months.'M');
    }
}

4

W połączeniu z odpowiedzią Shamittomara mogłoby to oznaczać „bezpieczne” dodanie miesięcy:

/**
 * Adds months without jumping over last days of months
 *
 * @param \DateTime $date
 * @param int $monthsToAdd
 * @return \DateTime
 */

public function addMonths($date, $monthsToAdd) {
    $tmpDate = clone $date;
    $tmpDate->modify('first day of +'.(int) $monthsToAdd.' month');

    if($date->format('j') > $tmpDate->format('t')) {
        $daysToAdd = $tmpDate->format('t') - 1;
    }else{
        $daysToAdd = $date->format('j') - 1;
    }

    $tmpDate->modify('+ '. $daysToAdd .' days');


    return $tmpDate;
}

Dziękuję bardzo!!
gekony

2

Znalazłem krótszy sposób obejścia tego za pomocą następującego kodu:

                   $datetime = new DateTime("2014-01-31");
                    $month = $datetime->format('n'); //without zeroes
                    $day = $datetime->format('j'); //without zeroes

                    if($day == 31){
                        $datetime->modify('last day of next month');
                    }else if($day == 29 || $day == 30){
                        if($month == 1){
                            $datetime->modify('last day of next month');                                
                        }else{
                            $datetime->modify('+1 month');                                
                        }
                    }else{
                        $datetime->modify('+1 month');
                    }
echo $datetime->format('Y-m-d H:i:s');

1

Oto implementacja ulepszonej wersji odpowiedzi Juhany na powiązane pytanie:

<?php
function sameDateNextMonth(DateTime $createdDate, DateTime $currentDate) {
    $addMon = clone $currentDate;
    $addMon->add(new DateInterval("P1M"));

    $nextMon = clone $currentDate;
    $nextMon->modify("last day of next month");

    if ($addMon->format("n") == $nextMon->format("n")) {
        $recurDay = $createdDate->format("j");
        $daysInMon = $addMon->format("t");
        $currentDay = $currentDate->format("j");
        if ($recurDay > $currentDay && $recurDay <= $daysInMon) {
            $addMon->setDate($addMon->format("Y"), $addMon->format("n"), $recurDay);
        }
        return $addMon;
    } else {
        return $nextMon;
    }
}

W tej wersji $createdDatezakłada się, że masz do czynienia z powtarzającym się okresem miesięcznym, takim jak subskrypcja, który rozpoczął się w określonym dniu, na przykład 31. Zawsze trwa $createdDatetak późno, że daty „powtarzające się” nie zmieniają się na niższe wartości, ponieważ są przesuwane do przodu przez miesiące o niższej wartości (np. Aby wszystkie 29, 30 lub 31 daty powtarzania się nie utknęły w 28 dniu po upływie przez luty niebędący rokiem przestępnym).

Oto kod sterownika do testowania algorytmu:

$createdDate = new DateTime("2015-03-31");
echo "created date = " . $createdDate->format("Y-m-d") . PHP_EOL;

$next = sameDateNextMonth($createdDate, $createdDate);
echo "   next date = " . $next->format("Y-m-d") . PHP_EOL;

foreach(range(1, 12) as $i) {
    $next = sameDateNextMonth($createdDate, $next);
    echo "   next date = " . $next->format("Y-m-d") . PHP_EOL;
}

Które wyjścia:

created date = 2015-03-31
   next date = 2015-04-30
   next date = 2015-05-31
   next date = 2015-06-30
   next date = 2015-07-31
   next date = 2015-08-31
   next date = 2015-09-30
   next date = 2015-10-31
   next date = 2015-11-30
   next date = 2015-12-31
   next date = 2016-01-31
   next date = 2016-02-29
   next date = 2016-03-31
   next date = 2016-04-30

1

To jest ulepszona wersja odpowiedzi Kasihasi na powiązane pytanie. Spowoduje to poprawne dodanie lub odjęcie dowolnej liczby miesięcy do daty.

public static function addMonths($monthToAdd, $date) {
    $d1 = new DateTime($date);

    $year = $d1->format('Y');
    $month = $d1->format('n');
    $day = $d1->format('d');

    if ($monthToAdd > 0) {
        $year += floor($monthToAdd/12);
    } else {
        $year += ceil($monthToAdd/12);
    }
    $monthToAdd = $monthToAdd%12;
    $month += $monthToAdd;
    if($month > 12) {
        $year ++;
        $month -= 12;
    } elseif ($month < 1 ) {
        $year --;
        $month += 12;
    }

    if(!checkdate($month, $day, $year)) {
        $d2 = DateTime::createFromFormat('Y-n-j', $year.'-'.$month.'-1');
        $d2->modify('last day of');
    }else {
        $d2 = DateTime::createFromFormat('Y-n-d', $year.'-'.$month.'-'.$day);
    }
    return $d2->format('Y-m-d');
}

Na przykład:

addMonths(-25, '2017-03-31')

wyświetli:

'2015-02-28'

0

Jeśli chcesz po prostu uniknąć pomijania miesiąca, możesz wykonać coś takiego, aby uzyskać datę i uruchomić pętlę w następnym miesiącu, zmniejszając datę o jeden i sprawdzając ponownie, aż do poprawnej daty, gdzie $ start_calculated jest prawidłowym ciągiem dla strtotime (tj. mysql datetime lub „now”). To wyszukuje sam koniec miesiąca na 1 minutę przed północą zamiast pomijania miesiąca.

    $start_dt = $starting_calculated;

    $next_month = date("m",strtotime("+1 month",strtotime($start_dt)));
    $next_month_year = date("Y",strtotime("+1 month",strtotime($start_dt)));

    $date_of_month = date("d",$starting_calculated);

    if($date_of_month>28){
        $check_date = false;
        while(!$check_date){
            $check_date = checkdate($next_month,$date_of_month,$next_month_year);
            $date_of_month--;
        }
        $date_of_month++;
        $next_d = $date_of_month;
    }else{
        $next_d = "d";
    }
    $end_dt = date("Y-m-$next_d 23:59:59",strtotime("+1 month"));


0

Jeśli używasz, strtotime()po prostu użyj$date = strtotime('first day of +1 month');


0

Musiałem znaleźć datę na „ten miesiąc w zeszłym roku” i dość szybko staje się nieprzyjemna, gdy ten miesiąc jest luty w roku przestępnym. Uważam jednak, że to działa ...: - / Wydaje się, że sztuczka polega na tym, aby oprzeć swoją zmianę na pierwszym dniu miesiąca.

$this_month_last_year_end = new \DateTime();
$this_month_last_year_end->modify('first day of this month');
$this_month_last_year_end->modify('-1 year');
$this_month_last_year_end->modify('last day of this month');
$this_month_last_year_end->setTime(23, 59, 59);

0
$ds = new DateTime();
$ds->modify('+1 month');
$ds->modify('first day of this month');

1
Musisz wyjaśnić swoją odpowiedź. Odpowiedzi zawierające tylko kod są uważane za niskiej jakości
Machavity

Dziękuję Ci! To jest jak dotąd najlepsza odpowiedź. Jeśli zmienisz ostatnie 2 wiersze, zawsze podaje właściwy miesiąc. Sława!
Danny Schoemann

0
$month = 1; $year = 2017;
echo date('n', mktime(0, 0, 0, $month + 2, -1, $year));

wyjdzie 2(luty). będzie działać również przez inne miesiące.


0
$current_date = new DateTime('now');
$after_3_months = $current_date->add(\DateInterval::createFromDateString('+3 months'));

Dniami:

$after_3_days = $current_date->add(\DateInterval::createFromDateString('+3 days'));

Ważny:

Metoda add()klasy DateTime modyfikuje wartość obiektu, więc po wywołaniu add()obiektu DateTime zwraca nowy obiekt daty, a także modyfikuje sam obiekt.


0

możesz to zrobić za pomocą po prostu date () i strtotime (). Na przykład, aby dodać 1 miesiąc do dzisiejszej daty:

date("Y-m-d",strtotime("+1 month",time()));

jeśli chcesz użyć klasy datetime, to też jest w porządku, ale jest to równie łatwe. więcej szczegółów tutaj


0

Przyjęta odpowiedź już wyjaśnia, dlaczego to nie jest ale, a niektóre inne odpowiedzi stanowią zgrabne rozwiązanie z wyrażeniami php, takimi jak first day of the +2 months. Problem z tymi wyrażeniami polega na tym, że nie są one automatycznie uzupełniane.

Rozwiązanie jest jednak dość proste. Najpierw powinieneś znaleźć przydatne abstrakcje, które odzwierciedlają twoją przestrzeń problemową. W tym przypadku jest to plik ISO8601DateTime. Po drugie, powinno być wiele implementacji, które mogą przynieść pożądaną reprezentację tekstową. Na przykład Today, Tomorrow, The first day of this month, Future- wszystkie stanowią realizację specyficznej ISO8601DateTimekoncepcji.

Tak więc w twoim przypadku implementacja, której potrzebujesz, to TheFirstDayOfNMonthsLater. Łatwo to znaleźć, patrząc na listę podklas w dowolnym IDE. Oto kod:

$start = new DateTimeParsedFromISO8601String('2000-12-31');
$firstDayOfOneMonthLater = new TheFirstDayOfNMonthsLater($start, 1);
$firstDayOfTwoMonthsLater = new TheFirstDayOfNMonthsLater($start, 2);
var_dump($start->value()); // 2000-12-31T00:00:00+00:00
var_dump($firstDayOfOneMonthLater->value()); // 2001-01-01T00:00:00+00:00
var_dump($firstDayOfTwoMonthsLater->value()); // 2001-02-01T00:00:00+00:00

To samo z ostatnimi dniami miesiąca. Więcej przykładów takiego podejścia, to przeczytać .


Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.