Strona 1 z 1

[Algorytmy] Kilka operacji na datach

: wtorek 23 sty 2018, 23:48
autor: gaweł
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