Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

> [php] Sumowanie zakresów wielu dat, Czyli ile miesięcy minęło między podanymi zakresami dat
mlodyno
post
Post #1





Grupa: Zarejestrowani
Postów: 3
Pomógł: 0
Dołączył: 22.10.2008

Ostrzeżenie: (0%)
-----


Witam,

chciałbym obliczyć ilość miesięcy jaka upłynęła między datami. Nie jest to standardowy przypadek ponieważ trzeba wziąć pod uwagę zazębianie się dat. Najłatwiej przedstawić to na przykładzie urlopów

1. Urlop między 2012-01-01 a 2012-06-31
2. Urlop między 2012-04-01 a 2012-07-31
3. Urlop między 2012-05-01 a 2012-05-31
4. Urlop między 2012-10-01 a 2012-11-31

Pytanie: ile miesięcy był na urlopie? (oczywiście zazębienie dat powinno być wykrywane na etapie dodawania urlopów, ale potrzebuje to do innego skryptu a na przykładzie urlopów najłatwiej wytłumaczyć).

Urlop 1,2 i 3 jest zazębiony. Oczywiście daty mogą być zazębione w różny inny sposób.
W wyniku powinienem dostać odpowiedź: 9 miesięcy, czyli nie jest to suma(urlop1;urlop2;urlop3;urlop4).


Do tej pory poradziłem sobie tak, że wpisuje do tablicy miesiące poszczególnych urlopów czyli:
  1. $tab = array(2012-01-01, 2012-02-01, 2012-03-01, 2012-04-01.... , 2012-06-01),
  2. array(2012-04-01, 2012-05-01, 2012-06-01, 2012-07-01),
  3. array(2012-05-01),
  4. array(2012-10-01, 2012-11-01) );

I teraz usuwam duplikaty i otrzymuje unikalne miesiące, których suma daje mi długość urlopów.
Problem w tym, że wykonuje się to bardzo długo, bo 7 minut dla 7000 rekordów (w końcu powyższe operacje wykonuje osobno dla każdego rekordu).

Może inne pomysły związane z operacjami na zakresach dat?
Go to the top of the page
+Quote Post
 
Start new topic
Odpowiedzi (1 - 9)
CuteOne
post
Post #2





Grupa: Zarejestrowani
Postów: 2 958
Pomógł: 574
Dołączył: 23.09.2008
Skąd: wiesz, że tu jestem?

Ostrzeżenie: (0%)
-----


Hmm trudna sprawa (IMG:style_emoticons/default/smile.gif)

  1. while($row = mysql_fetch_assoc()) {
  2.  
  3. $start = explode('-', $row['start']);
  4. $end = explode('-', $row['end']);
  5.  
  6. for($y=$start[0]; $y <= $end[0]; $y++) {
  7.  
  8. // pętla dla miesięcy
  9.  
  10. $array[$y][$m] = 1;
  11. }
  12. }
  13. }
  14.  
  15.  
  16. $array = array(
  17. '2012' => array(
  18. '01'=>1,
  19. '02'=>1,
  20. '03'=>1,
  21. '04'=>1,
  22. '08'=>1,
  23. '09'=>1
  24. )
  25. );

sumujesz miesiące w poszczególnych latach i gotowe

Ten post edytował CuteOne 10.09.2012, 11:44:48
Go to the top of the page
+Quote Post
mlodyno
post
Post #3





Grupa: Zarejestrowani
Postów: 3
Pomógł: 0
Dołączył: 22.10.2008

Ostrzeżenie: (0%)
-----


Cytat(CuteOne @ 10.09.2012, 12:41:59 ) *
Hmm trudna sprawa (IMG:style_emoticons/default/smile.gif)

sumujesz miesiące w poszczególnych latach i gotowe


wygląda ok i na pierwszy rzut oka powinno działać. Po południu sprawdzę, choć rzuca się w oczy pętla dla miesięcy. Jeśli w 2011 roku był start od października do 2012 stycznia to pętla będzie od 10 do 1.
Go to the top of the page
+Quote Post
melkorm
post
Post #4





Grupa: Zarejestrowani
Postów: 1 366
Pomógł: 261
Dołączył: 23.09.2008
Skąd: Bydgoszcz

Ostrzeżenie: (0%)
-----


Tę pętle lepiej zamienić na:
  1. $dateFrom new \DateTime($row['start']);
  2. $dateTo = new \DateTime($row['end']);
  3. $diff = $dateFrom->diff($dateTo);
  4. $months = $diff->format('m');


Wtedy nie mamy problemu z latami.
Go to the top of the page
+Quote Post
abort
post
Post #5





Grupa: Zarejestrowani
Postów: 590
Pomógł: 107
Dołączył: 25.10.2011

Ostrzeżenie: (0%)
-----


A ja bym poszedł innym tropem. Po pierwsze, nie liczyłbym miesięcy, tylko dni. No chyba że masz miesiące - ale co w przypadku, kiedy nieobecność trwa pół miesiąca (od 1szego do 15tego), a drugą połowę delikwent chodzi do pracy? Jeżeli przeliczasz na 0.5, to mój sposób może zadziałać.

A na czym mój sposób bazuje? Na lekkim brute-force, niestety (IMG:style_emoticons/default/smile.gif) Ale jest po pierwsze: skuteczny, po drugie: znacznie szybszy od Twojego (przetwarza 100 wpisów urlopowych w 0.02 sekundy, tak mi przynajmniej zeznaje microtime).
I mimo że nie lubię rzucać gotowym kodem, to tym razem chyba się nie da. Za to komentarza nie będzie, bo powinien dokumentować się sam. (IMG:style_emoticons/default/smile.gif)
  1. <?php
  2.  
  3. $urlopy = array (
  4. '1' => array ('from' => '2012-01-01', 'to' => '2012-06-31'),
  5. '2' => array ('from' => '2012-04-01', 'to' => '2012-07-31'),
  6. '3' => array ('from' => '2012-05-01', 'to' => '2012-05-31'),
  7. '4' => array ('from' => '2012-10-01', 'to' => '2012-11-31'),
  8. [... i tak dalej...]
  9. );
  10.  
  11. $urlopy_dni = array ();
  12.  
  13. foreach ($urlopy as $idx=>$okres) {
  14. list($year,$month,$day) = explode ('-', $okres['from']);
  15. $od = date ('z', mktime (0, 0, 0, $month, $day, $year));
  16.  
  17. list($year,$month,$day) = explode ('-', $okres['to']);
  18. $do = date ('z', mktime (0, 0, 0, $month, $day, $year));
  19.  
  20. for ($i=$od; $i<=$do; $i++) {
  21. $urlopy_dni[$i] = 1;
  22. }
  23. }
  24.  
  25. echo 'Całkowitych urlopów: ' . count ($urlopy_dni) . ' dni<br>';


Hm, w zasadzie to przerobienie na liczenie miesięcy nie będzie takie trudne. Po explode pomijamy części $year i $day, tablicę $urlopy_dni zmieniamy np. na $urlopy_miesiace, o date i mktime zapominamy, a do tablicy wpisujemy "1", jeśli zahaczyliśmy o miesiąc. Tylko że znowu pozostaje pytanie: czy tydzień urlopu od 2012-02-27 do 2012-03-04 to "tylko" 5 dni urlopu czy "aż" dwa miesiące? Bo to chyba nie będzie to samo...

EDIT
Tak czytam jeszcze raz oryginalny post z problemem... i tak sobie myślę... najpierw dopisywanie, potem usuwanie duplikatów... Strasznie to skomplikowane - dopisywanie jest proste, owszem. Ale usuwanie duplikatów z tablicy, i to tak skonstruowanej jak u Ciebie? Bierzemy pierwszy element z pierwszej tablicy i szukamy go we wszystkich tablicach i usuwamy. Potem drugi element z pierwszej, trzeci. A potem pierwszy element z drugiej, tak? No to złożoność obliczeniową masz mega (IMG:style_emoticons/default/smile.gif)
Rada: zmodyfikuj lekko swój algorytm i nie dodawaj całych dat, tylko rozbij to na rok i miesiąc i twórz indeksy i używaj w postaci
  1. $urlop[$rok.'-'.$mc] = 1

Ustawienie zmiennej nawet kilka tysięcy razy będzie dużo szybsze niż przeszukiwanie tablicy. A do liczenia elementów swojej tablicy masz szybkie i wygodne count()

Ten post edytował abort 10.09.2012, 23:20:54
Go to the top of the page
+Quote Post
wNogachSpisz
post
Post #6





Grupa: Zarejestrowani
Postów: 1 233
Pomógł: 87
Dołączył: 6.03.2009

Ostrzeżenie: (40%)
XX---


1. Podziel na stringi "YYYMM"
2. Wrzuc do tymczasowej bazy (sqlite:memory albo MySQL:Temporary_table)
3. Wykonaj zapyta SELECT DISTINCT
Go to the top of the page
+Quote Post
CuteOne
post
Post #7





Grupa: Zarejestrowani
Postów: 2 958
Pomógł: 574
Dołączył: 23.09.2008
Skąd: wiesz, że tu jestem?

Ostrzeżenie: (0%)
-----


Sposobów na rozwiązanie problemu jest masa... pytanie co dokładnie chcesz otrzymać - liczbę miesięcy czy liczbę dni.

Edit:
@2xUP kod wydaje się w porządku (nie mam możliwości testów) ale hmm.. (IMG:style_emoticons/default/tongue.gif) idźmy z duchem czasu i korzystajmy z wbudowanych narzędzi jakie zaproponował melkorm

  1. $days = 0;
  2. foreach ($urlopy as $idx=>$okres) {
  3. $dateFrom = new \DateTime($okres['start']);
  4. $dateTo = new \DateTime($okres['to']);
  5. $diff = $dateFrom->diff($dateTo);
  6. $days += $diff->format('d');
  7. }

(IMG:style_emoticons/default/smile.gif)

Edit2:
fakt zapomniałem o zazębianiu dat.

Ten post edytował CuteOne 11.09.2012, 08:47:02
Go to the top of the page
+Quote Post
wNogachSpisz
post
Post #8





Grupa: Zarejestrowani
Postów: 1 233
Pomógł: 87
Dołączył: 6.03.2009

Ostrzeżenie: (40%)
XX---


@up odpowiedź masz w pierwszym poście.
Twój kod nie zadziała, bo nie bierze pod uwage zazębiania się dat. Dodaje jak leci, nie patrzy na nic.

Ten post edytował wNogachSpisz 11.09.2012, 08:32:17
Go to the top of the page
+Quote Post
mlodyno
post
Post #9





Grupa: Zarejestrowani
Postów: 3
Pomógł: 0
Dołączył: 22.10.2008

Ostrzeżenie: (0%)
-----


Dziękuję wszystkim za aktywną pomoc. Testuję po kolei różne rozwiązania... powinienem jutro dać znać, które okazało się najszybsze. A piszę teraz żebyście nie pomyśleli, że "otrzymał pomoc = zapomniał".


Metoda Abort, która polegała na zazosotwanie indeksów w tablicy okazała się najszybsza. O dziwo nie był to ogromny wzrost szybkości ale jednak. Rozwiązanie proste ale skuteczne.

Niestety skrypt generuje się i tak 6 minut z racji wielu innych opetacji. Jest 7 000 rekordów i dla każdego z nich muszę przypisać dane z drugiej tabeli B gdzie mam 100 000 rekordów... Z kolei na każdym rekordzie z tabeli B muszę zrobić proste operacje na datach (rrrr-mm-dd + 1 miesiąc) itp. Każda z sekcji wykonuje się ok ale przy uruchomieniu wszystkich czas się wydłuża. Cachuje wyniki więc nie jest najgorszej. Dzięki wszystkim za pomoc!
Go to the top of the page
+Quote Post
abort
post
Post #10





Grupa: Zarejestrowani
Postów: 590
Pomógł: 107
Dołączył: 25.10.2011

Ostrzeżenie: (0%)
-----


Cytat(mlodyno @ 13.09.2012, 13:43:53 ) *
Niestety skrypt generuje się i tak 6 minut z racji wielu innych opetacji. Jest 7 000 rekordów i dla każdego z nich muszę przypisać dane z drugiej tabeli B gdzie mam 100 000 rekordów... Z kolei na każdym rekordzie z tabeli B muszę zrobić proste operacje na datach (rrrr-mm-dd + 1 miesiąc) itp. Każda z sekcji wykonuje się ok ale przy uruchomieniu wszystkich czas się wydłuża. Cachuje wyniki więc nie jest najgorszej. Dzięki wszystkim za pomoc!

Z bazami nie pomogę zbyt wiele, bo wielkiego doświadczenia nie mam - ale czy nie myślałeś o tym, aby przynajmniej część tej "czarnej" roboty (że wspomnę tylko o przypisywaniu danych - to można zrobić złączeniem/JOIN) zrzucić na bazę danych? Przy dobrze zaprojektowanej bazie danych mogłoby to przynieść znaczny wzrost wydajności...
Jeśli to rozważasz, to są tu na forum mocarze, którzy z pewnością coś pomogą... Poczytaj sobie subforum 'bazy danych'
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
2 Użytkowników czyta ten temat (2 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 25.12.2025 - 20:22