[Algorytmy] Kilka operacji na datach

Projekty użytkowników forum zarówno sprzętowe, jak i związane z programowaniem w dowolnym języku.
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

[Algorytmy] Kilka operacji na datach

Postautor: gaweł » wtorek 23 sty 2018, 23:48

Czas – zabawy z datami *
* tekst powstał jesienią 2005 roku
time_f00.jpg


Jak co roku jesienią nadchodzi pora przejścia z czasu zimowego na czas letni. Odwieczny rytuał przestawiania wszystkich zegarków o jedną godzinę. Chodząc z pokoju do pokoju i aktualizując wskazania zegarów naściennych wzrastał we mnie poziom dezaprobaty. Czy zegarki nie mogłyby same się przestawiać? W gruncie rzeczy by mogły (z kilkoma wyjątkami zegarków nie mających mikrokontrolera w sobie). Zaprzątała mnie myśl jak zrealizować tą czynność w sposób programowy. Wiadomo przecież, że zmiana czasu (ta z letniego na zimowy) następuje o godzinie 3 w nocy z soboty na niedzielę (czyli właściwie w niedzielę) w ostatni weekend października. Jak określić w programie, datę kiedy należy wykonać akcję przestawienia zegara. I tak chodząc od zegara do zegara (a mam ich ze sześć sztuk) w mojej podświadomości jakiś programista układał algorytm dla programu i nagle ... eureka. Zamiast określać kiedy ma nastąpić zmiana czasu (w sensie określenia daty i czasu wykonania akcji – typowej akcji budzika) można podejść do problemu z innej strony. Należy sprawdzić czy tą akcję należy wykonać dzisiaj, a to nie jest już skomplikowane.
Wiadomo, że zmiana czasu następuje w ostatnią niedzielę października. W grę wchodzą daty od 25 października do 31 października włącznie (czyli ostatni tydzień), a właściwie to wystarczy sprawdzić, czy numer dnia jest większy od 25 (bo mniejszy lub równy 31 będzie zawsze). Należy tylko sprawdzić czy dany dzień w tym przedziale jest niedzielą. Proste. Już kiedyś miałem sposobność programowej zabawy z datami i dniami tygodnia. Algorytm jest prosty. Należy obliczyć liczbę dni jakie upłynęły od wybranego konkretnego dnia do dnia, który jest badany. Odgrzebałem jakiś stary program, w którym takie zabawy były robione. W moich procedurach wybranym dniem jest 1 styczeń 1900 roku. Pozwala to na określenia dnia tygodnia dla dowolnego dnia poczynając od początku XX wieku. Mając obliczoną liczbę dni jaka minęła od wybranego dnia, obliczamy resztę z dzielenia tej liczby przez 7. Wynik (reszta z dzielenia) określa nam dzień tygodnia. Dla przyjętych warunków początkowych jest to:
  • poniedziałek jeżeli reszta z dzielenia wynosi 2
  • wtorek jeżeli reszta z dzielenia wynosi 3
  • środa jeżeli reszta z dzielenia wynosi 4
  • czwartek jeżeli reszta z dzielenia wynosi 5
  • piątek jeżeli reszta z dzielenia wynosi 6
  • sobota jeżeli reszta z dzielenia wynosi 0
  • niedziela jeżeli reszta z dzielenia wynosi 1
W przypadku algorytmów, jako działań w dużej mierze oderwanych od środowiska, pewne fragmenty można przetestować przykładowo w komputerze PC. Pozwala to na prostszą diagnostykę. Odpalam kompilator, piszę kawałek programu i przeprowadzam testy dla bieżącego roku. Wynik: działa z pierwszego kopa (nawet nie ma gdzie się pomylić, bo procedurki “obróbki” dat wyciągam jako gotowe i sprawdzone ze starego programu). Przy okazji można zauważyć pewną uniwersalność algorytmu. Dysponując funkcją zamieniająca określoną datę na liczbę dni jakie minęła od 1 stycznie 1900 można obliczać liczbę dni jaka minęła od określonej daty do innej daty (oczywiście obie daty muszą być XX-wieczne lub późniejsze). Wystarczy przeliczyć określoną datę na postać liczbową (reprezentującą liczbę dnie jaka minęła od początku XX wieku), identycznie przeliczyć drugą datę i obliczyć ich różnicę, która będzie reprezentować sobą liczbę dni, jaka dzieli obie daty.
Program jest następujący (w wersji na komputer PC):

Kod: Zaznacz cały

#include "stdio.h"

#define Nie     1
#define Pon     2
#define Wto     3
#define Sro     4
#define Czw     5
#define Pia     6
#define Sob     0


unsigned short ZamienDateNaLiczbe ( unsigned short Rok ,
                                    unsigned short Miesiac ,
                                    unsigned short Dzien )
  {
    unsigned short RokDiv ;
    unsigned short RokMod ;
    unsigned short Wynik ;
    unsigned short DniWLutym ;
    /* ------------------- */
    Rok = Rok - 1900 ;
    RokDiv = Rok / 4 ;
    RokMod = Rok % 4 ;
    Wynik = Rok * 365 + RokDiv ;
    if ( RokMod )
    {
      DniWLutym = 28 ;
      Wynik ++ ;
    } /* if ... */
    else
      DniWLutym = 29 ;
    switch ( Miesiac )
    {
      case 1   :
        break ;
      case 2   : /* minal styczen  (+31) */
        Wynik += 31 ;
        break ;
      case 3   : /* minal luty     (+31+DniWLutym) */
        Wynik += 31 + DniWLutym ;
        break ;
      case 4   : /* minal marzec   (+31+DniWLutym+31) */
        Wynik += 62 + DniWLutym ;
        break ;
      case 5   : /* minal kwiecien (+31+DniWLutym+31+30) */
        Wynik += 92 + DniWLutym ;
        break ;
      case 6   : /* minal maj      (+31+DniWLutym+31+30+31) */
        Wynik += 123 + DniWLutym ;
        break ;
      case 7   : /* minal czerwiec (+31+DniWLutym+31+30+31+30) */
        Wynik += 153 + DniWLutym ;
        break ;
      case 8   : /* minal lipec    (+31+DniWLutym+31+30+31+30+31) */
        Wynik += 184 + DniWLutym ;
        break ;
      case 9   : /* minal sierpien (+31+DniWLutym+31+30+31+30+31+31) */
        Wynik += 215 + DniWLutym ;
        break ;
      case 10  : /* minal wrzesien (+31+DniWLutym+31+30+31+30+31+
                                                             31+30) */
        Wynik += 245 + DniWLutym ;
        break ;
      case 11  : /* minal pazdziernik (+31+DniWLutym+31+30+31+30+31+
                                                          31+30+31) */
        Wynik += 276 + DniWLutym ;
        break ;
      case 12  : /* minal listopad    (+31+DniWLutym+31+30+31+30+31+
                                                       31+30+31+30) */
        Wynik += 306 + DniWLutym ;
        break ;
      default :
        break ;
    } /* switch */ ;
    return ( Wynik + Dzien ) ;
  } /* ZamienDateNaLiczbe */


unsigned short DzienTygodnia ( unsigned short Rok ,
                               unsigned short Miesiac ,
                               unsigned short Dzien )
  {
    return ( ZamienDateNaLiczbe ( Rok , Miesiac , Dzien ) % 7 ) ;
  } /* DzienTygodnia */


int ZmianaCzasuNaZimowy ( unsigned short Rok ,
                          unsigned short Miesiac ,
                          unsigned short Dzien )
  {
    int FlagaZmiany = 0 ;
    /* ------------------- */
    if ( Miesiac == 10 )
      if ( Dzien >= 25 )
        if ( DzienTygodnia ( Rok , Miesiac , Dzien ) == Nie )
          FlagaZmiany = 1 ;
    return ( FlagaZmiany ) ;
  } /* ZmianaCzasuNaZimowy */


int main ( void )
  {
    unsigned short Dzien ;
    unsigned short Miesiac ;
    unsigned short Rok ;
    /* ------------------- */
    Miesiac = 1 ;
    Rok = 2006 ;
    Dzien = 1 ;
    printf ( "Najblizszy nowy rok jest w " );
    switch ( DzienTygodnia ( Rok , Miesiac , Dzien ) )
    {
      case Nie     :
        printf ( "niedziele\n" ) ;
        break ;
      case Pon     :
        printf ( "poniedzialek\n" ) ;
        break ;
      case Wto     :
        printf ( "wtorek\n" ) ;
        break ;
      case Sro     :
        printf ( "sroda\n" ) ;
        break ;
      case Czw     :
        printf ( "czwartek\n" ) ;
        break ;
      case Pia     :
        printf ( "piated\n" ) ;
        break ;
      case Sob     :
        printf ( "sobote\n" ) ;
        break ;
      default      :
        ;
    } /* switch */ ;
    Miesiac = 10 ;
    Rok = 2005 ;
    for ( Dzien = 20 ; Dzien <= 31 ; Dzien ++ )
    {
      if ( ZmianaCzasuNaZimowy ( Rok , Miesiac , Dzien ) )
        printf ( "Zmiana czasu w %d.%d.%d : niedziela\n" ,
                        Dzien , Miesiac , Rok ) ;
    } /* for */ ;
  } /* main */
Uruchamiam program. Testowo podaję kilka dat a program określa dzień tygodnia. Przykładowo Nowy Rok w roku 2006 mamy w niedzielę. Zaglądam do kalendarza i okazuje się, że to prawda. Inna data... wychodzi, że urodziłem się w sobotę. Może to i prawda, nie pamiętam ...
Oczywiście nie jest to pełny algorytm. Do pełni szczęścia należałoby przechowywać gdzieś w pamięci nieulotnej aktualny rodzaj czasu. Niech to będzie zmienna o nazwie CzasLetni, która przyjmuje wartość 0 jeżeli jest czas zimowy lub 1 jeżeli jest czas letni.

Kod: Zaznacz cały

....
unsigned char  CzasLetni


for ( ; ; )
{
  PobierzCzasIDate ( ) ;
  if ( CzasLetni )
  {
    if ( ZmianaCzasuNaZimowy ( Rok , Miesiac , Dzien ) )
    {
      if ( Godz == 3 )
      {
        CzasLetni = 0 ;
        ZmianaCzasuZLetniegoNaZimowy ( ) ;
      } /* if */ ;
    } /* if */ ;
  } /* if ... */
  else
  {
//..............
  } /* if ... else */ ;
} /* for */ ;
Szkielet takiego programu może wyglądać jak wyżej. Zmiany zawartości zmiennej CzasLetni muszą być odnotowane w pamięci nieulotnej. Program kręci się w pętli i w przypadku czasu letniego [prawdziwości warunku if ( CzasLetni )] po stwierdzeniu, że należy dokonać zmiany czasu [prawdziwości warunku if ( ZmianaCzasuNaZimowy... oraz if ( Godz == 3 )] następuje zmiana czasu i przełącznika rodzaju czasu na czas zimowy. Od tej chwili program będzie kręcił się w wariancie else (nie dokona wielokrotnej zmiany czasu z letniego na zimowy). Co powinno być w wariancie obsługi dla czasu zimowego? To ... pomyślę wiosną, teraz mi się nie chce.
Czas przenieść algorytmy do środowiska procków (przykładowo AVR) i sprawdzić dziłanie.

Kod: Zaznacz cały

(…)

#include <inttypes.h>
#include <avr/io.h>


#define nop() __asm__ __volatile__ ("nop")

#define Nie     1
#define Pon     2
#define Wto     3
#define Sro     4
#define Czw     5
#define Pia     6
#define Sob     0


uint16_t ZamienDateNaLiczbe ( uint16_t Rok ,
                              uint16_t Miesiac ,
                              uint16_t Dzien )
  {
    uint16_t RokDiv ;
    uint16_t RokMod ;
    uint16_t Wynik ;
    uint16_t DniWLutym ;
    /* ------------------- */
    Rok = Rok - 1900 ;
    RokDiv = Rok / 4 ;
    RokMod = Rok % 4 ;
    Wynik = Rok * 365 + RokDiv ;
    if ( RokMod )
    {
      DniWLutym = 28 ;
      Wynik ++ ;
    } /* if ... */
    else
      DniWLutym = 29 ;
    switch ( Miesiac )
    {
      case 1   :
        break ;
      case 2   : /* minal styczen     (+31) */
        Wynik += 31 ;
        break ;
      case 3   : /* minal luty        (+31+DniWLutym) */
        Wynik += 31 + DniWLutym ;
        break ;
      case 4   : /* minal marzec      (+31+DniWLutym+31) */
        Wynik += 62 + DniWLutym ;
        break ;
      case 5   : /* minal kwiecien    (+31+DniWLutym+31+30) */
        Wynik += 92 + DniWLutym ;
        break ;
      case 6   : /* minal maj         (+31+DniWLutym+31+30+31) */
        Wynik += 123 + DniWLutym ;
        break ;
      case 7   : /* minal czerwiec    (+31+DniWLutym+31+30+31+30) */
        Wynik += 153 + DniWLutym ;
        break ;
      case 8   : /* minal lipec       (+31+DniWLutym+31+30+31+30+31) */
        Wynik += 184 + DniWLutym ;
        break ;
      case 9   : /* minal sierpien    (+31+DniWLutym+31+30+31+30+31+31) */
        Wynik += 215 + DniWLutym ;
        break ;
      case 10  : /* minal wrzesien    (+31+DniWLutym+31+30+31+30+31+31+30) */
        Wynik += 245 + DniWLutym ;
        break ;
      case 11  : /* minal pazdziernik (+31+DniWLutym+31+30+31+30+31+31+30+31)*/
        Wynik += 276 + DniWLutym ;
        break ;
      case 12  : /* minal listopad (+31+DniWLutym+31+30+31+30+31+31+30+31+30)*/
        Wynik += 306 + DniWLutym ;
        break ;
      default :
        break ;
    } /* switch */ ;
    return ( Wynik + Dzien ) ;
  } /* ZamienDateNaLiczbe */


uint16_t DzienTygodnia ( uint16_t Rok ,
                         uint16_t Miesiac ,
                         uint16_t Dzien )
  {
    return ( ZamienDateNaLiczbe ( Rok , Miesiac , Dzien ) % 7 ) ;
  } /* DzienTygodnia */


uint8_t ZmianaCzasuNaZimowy ( uint16_t Rok ,
                              uint16_t Miesiac ,
                              uint16_t Dzien )
  {
    uint8_t FlagaZmiany = 0 ;
    /* ------------------- */
    if ( Miesiac == 10 )
      if ( Dzien >= 25 )
        if ( DzienTygodnia ( Rok , Miesiac , Dzien ) == Nie )
          FlagaZmiany = 1 ;
    return ( FlagaZmiany ) ;
  } /* ZmianaCzasuNaZimowy */


int main ( void )
  {
    uint16_t Dzien ;
    uint16_t Miesiac ;
    uint16_t Rok ;
    uint8_t Cos ;
    uint8_t Cos2 ;
    /* ------------------- */
    Cos = ' ' ;
    Cos2 = ' ' ;
    Miesiac = 1 ;
    Rok = 2006 ;
    Dzien = 1 ;
    switch ( DzienTygodnia ( Rok , Miesiac , Dzien ) )
    {
      case Nie     :
        Cos = 'N' ;
        break ;
      case Pon     :
        Cos = 'P' ;
        break ;
      case Wto     :
        Cos = 'W' ;
        break ;
      case Sro     :
        Cos = 'S' ;
        break ;
      case Czw     :
        Cos = 'C' ;
        break ;
      case Pia     :
        Cos = 'p' ;
        break ;
      case Sob     :
        Cos = 's' ;
        break ;
      default      :
        ;
    } /* switch */ ;
    Miesiac = 10 ;
    Rok = 2005 ;
    for ( Dzien = 20 ; Dzien <= 31 ; Dzien ++ )
    {
      if ( ZmianaCzasuNaZimowy ( Rok , Miesiac , Dzien ) )
      {
        Cos2 = 'Z' ;
   break ;
      } /* if */ ;
    } /* for */ ;
    /*
           w tym miejscu zmienna Cos2 powinna miec 'Z',
                    zmienna Dzien powinna miec 30
             zmienna Cos powinna miec 'N'

    */
    for ( ; ; )
    {
     nop ( ) ;
    } /* for */ ;
  } /* main */
Działanie programu zostało sprawdzone poprzez symulator wbudowany w środowisko AVR Studio (oczywiści otrzymane wyniki były zgodne z oczekiwanymi).

Załącznik z programer dla AVR'a:
czas_c.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Wróć do „DIY”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 8 gości