CodeGym /Kurslar /C++ SELF /Tarix və vaxtın saxlanması — sadə format strategiyaları

Tarix və vaxtın saxlanması — sadə format strategiyaları

C++ SELF
Səviyyə, Dərs
Mövcuddur

1. Niyə “vaxtı saxlamaq” kiçik bir müqavilədir

Proqrama vaxt əlavə etdiyiniz ilk anda, belə bir şey etmək üçün güclü cazibə var: long long t = ...; və bundan sonra rahat yaşamaq. Tam o vaxta qədər ki, saxlanmış məlumatları açıb qəflətən başa düşürsünüz: bu saniyədir? millisekunddur? UTC vaxtıdır? lokal vaxtdır? proqramın başlamasından nisbidir? “epox”dan nisbidir?

Proqramlaşdırmada vaxt yalnız “cari ədəd” deyil, həm də həmin ədədin mənası üzrə razılaşmadır. Yaxşı xəbər: normal başlamaq üçün saat qurşaqları və təqvim arifmetikasına daxil olmaq lazım deyil. Bir və ya iki sadə strategiya seçmək və onları layihənizin mini-standartı kimi saxlamaq kifayətdir.

Bu mühazirədə iki praktik iş görəcəyik: saxlanma üçün sadə formatlar (ədəd və ya sətir) seçəcəyik və time_point ↔ ədəd ↔ sətir üçün eyni konversiyanı quracağıq.

İki qat: “necə saxlayıram” və “necə göstərirəm”

Bu qatları qarışdırmamaq vacibdir. Proqram vaxtı saxlayanda adətən müqayisə etmək, sıralamaq, serializasiya etmək və dəqiqliyi itirməmək rahat olmalıdır. Proqram vaxtı insana göstərəndə isə oxunaqlı və tanış olmalıdır.

Buna görə sağlam vərdiş belə səslənir: daxili məntiqdə std::chrono (və ya aydın müəyyən edilmiş ədədi format) ilə işləyin, sərhəddə (ekrana çıxış, fayla yazma, JSON) isə “insana” uyğun görünüşə çevirin.

Əgər eyni anda “gözəl sətir” saxlamağa çalışsanız, tezliklə görəcəksiniz ki, sıralama qəribə olur, müqayisələr yavaşlayır, və parse etmək ağrılıdır. Əgər istifadəçiyə “epoxadan millisekundlar” göstərməyə çalışsanız, istifadəçi sizə kalkulyator kimi baxacaq, filosofiyyaya keçmiş kalkulyator kimi.

2. Anı epoxadan ədəd kimi saxlamaq

Tədris və bir çox tətbiq layihələri üçün ən praktik və populyar yol: vaxt anını tam ədəd kimi saxlamaq, yəni “epoxadan neçə vahid keçib” ədədini saxlamaq. system_clock üçün epoxa adətən Unix epoch-una uyğun gəlir (1970-01-01 00:00:00 UTC), amma indi daha vacib olan: vahidi seçirik və onu qarışdırmırıq.

Ən çox iki seçim seçilir:

Saxlama vahidi Tipik tip Müsbət cəhətlər Mənfi cəhətlər
saniyələr std::time_t və ya long long sadə, köhnə C API ilə uyğun dəqiqlik yalnız saniyələrədək
millisekundlar std::int64_t daha dəqiq, loqlar/ortaqlar üçün rahat time_point-a özümüz çevirməliyik

Bu mühazirədə mən millisekundları “ümumi başlanğıc” kimi tövsiyə edəcəyəm: bu bir çox tətbiqlər üçün kifayət qədər dəqiqdir, həm də hələ tam ədəd tipidir.

Konversiya: time_point ↔ epoxadan millisekundlar

İndi iki kiçik köməkçi funksiya: proqram daxilində biz std::chrono::system_clock::time_point ilə işləyirik, modellərdə/faylda saxlamaq üçün isə std::int64_t ms_since_epoch istifadə edirik.

time_point → ms_since_epoch

İdeya sadədir: time_point-ı time_since_epoch() vasitəsilə duration-a çevirib, sonra duration_cast ilə millisekundlara gətirmək. Sonra sadəcə count()-u almaq qalır.

#include <chrono>
#include <cstdint>

std::int64_t to_unix_ms(std::chrono::system_clock::time_point tp) {
    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
        tp.time_since_epoch()
    );
    return ms.count();
}

Burada count() “xam” ədədi tip qaytarır, amma biz onu yalnız sərhəddə istifadə edirik — çünki bu artıq saxlanma formatıdır.

ms_since_epoch → time_point

Əks əməliyyat fayldan/JSON-dan ədəd yükləndikdə və yenidən “normal insan” kimi işləmək istədikdə lazımdır, kalkulyator kimi deyil.

#include <chrono>
#include <cstdint>

std::chrono::system_clock::time_point from_unix_ms(std::int64_t ms_since_epoch) {
    return std::chrono::system_clock::time_point{ std::chrono::milliseconds{ms_since_epoch} };
}

Əslində bu dəqiq “an = epoxa + müddət” əməliyyatıdır.

Rəqəmsal vaxt üçün adlandırma mini-qaydası

“Sade ədəd” saxlayanda, std::chrono::milliseconds kimi tip qoruması yoxdur. Buna görə bunu adla kompensasiya edirsiniz.

Dəyişən adları bəzək deyil. Vaxt üçün bu əslində “səhvlərə qarşı təminat”dır. Əgər dəyişəni createdAt adlandırmısınızsa, bir ay sonra bunun nə olduğunu unutacaqsınız (saniyə? millisekund?).

Müqayisə üçün iki yanaşma:

std::int64_t createdAt = 1737040000000;     // nə olduğu aydın deyil
std::int64_t created_ms_since_epoch = 1737040000000; // artıq daha yaxşı

İkinci variant daha uzun olsa da, özünüzü aldatma ehtimalı azdır. Özünü aldanmış proqramçı ən təhlükəli proqramçıdır (xüsusən də ona prod-a çıxış imkanı verilibsə).

3. Alternativlər: std::time_t və sətir kimi saxlamaq

Bəzən millisekundlara ehtiyac yoxdur. Məsələn, siz qeyd yaradılma tarixini saxlayırsınız və sizə saniyə dəqiqliyi kifayətdir. Onda std::time_t tam normal seçimdir. Onun faydası odur ki, bir çox formatlaşdırma funksiyaları məhz onu gözləyir.

Bu din deyil və “nə daha düzgündür” barədə mübahisə üçün səbəb deyil. Bu tapşırığa görə seçimdir: əgər saniyə dəqiqliyi sizi qane edirsə, time_t sadə və aydın formatdır.

Budur system_clock::now()-dan time_t əldə etmənin yolu:

#include <chrono>
#include <ctime>

std::time_t now_as_time_t() {
    auto now = std::chrono::system_clock::now();
    return std::chrono::system_clock::to_time_t(now);
}

Və time_t-ı yenidən time_point-a çevirmək belədir:

#include <chrono>
#include <ctime>

std::chrono::system_clock::time_point from_time_t(std::time_t t) {
    return std::chrono::system_clock::from_time_t(t);
}

Əgər time_t saxlayırsınızsa, zehində (və layihənin sənədlərində) bunun saniyələr olduğunu mütləq sabitləyin.

Sətir kimi saxlama nə vaxt məntiqlidir

Sətir rahat görünür: faylı açdınız — dərhal tarixi görürsünüz. Amma sətirin bir qiyməti var: onu yaratmaq, parse etmək, yoxlamaq lazımdır, və o asanlıqla format zövqsüzlüyünə çevrilir (“16/01/26”, “01-16-2026”, “2026.01.16”, “cümə, amma dəqiq deyil”).

Sətir konfiqlər, insan üçün nəzərdə tutulmuş loqlar və ya insanın əl ilə redaktə edəcəyi format üçün yaxşıdır. Maşın üçün saxlanmada (xüsusən struktur daxilində) adətən ədəd daha rahatdır.

Lakin ən azı bizim üçün lazım olan: time_point-ı gözəl çap etməyi bilməkdir.

4. Təqvim vaxtını put_time ilə çap etmək

İndi klassik formatlama “kənar”ını toplayacağıq. O, <ctime> və <iomanip>-ə əsaslanır. Bəli, köhnə görünür, amma başa düşüləndir, stabildir və real kodda tez-tez rast gəlinir.

Burada vaxtın müxtəlif “təmsilləri” var: time_point hesablamalar üçün uyğundur, time_t uyğunluq üçün, tm — il/ay/gün kimi “parçalamaq” üçün, və put_time — şablon üzrə çap üçün.

Birbaşa std::cout-a çap

#include <chrono>
#include <ctime>
#include <iomanip>
#include <iostream>

int main() {
    auto now = std::chrono::system_clock::now();
    std::time_t t = std::chrono::system_clock::to_time_t(now);

    std::tm tm = *std::localtime(&t);
    std::cout << std::put_time(&tm, "%Y-%m-%d %H:%M:%S") << '\n'; // məsələn: 2026-01-16 14:05:31
}

Vacib məqama diqqət: std::localtime daxili statik obyektə göstərici qaytarır, ona görə onu dərhal std::tm tm = *...; şəklində kopyalayırıq. Bu daha təhlükəsiz və proqnozlaşdırılandır.

std::string-ə formatlama

Bəzən dərhal çap etmək deyil, sətir yaratmaq və sonra onu harasa yerləşdirmək istəyirsiniz: JSON-a, səhv mesajına, log-a.

#include <chrono>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <string>

std::string format_local(std::chrono::system_clock::time_point tp) {
    std::time_t t = std::chrono::system_clock::to_time_t(tp);
    std::tm tm = *std::localtime(&t);

    std::ostringstream out;
    out << std::put_time(&tm, "%Y-%m-%d %H:%M:%S");
    return out.str();
}

Bəli, burada ostringstream görünür, amma o əvvəllər sizinlə rastlaşıb (siz stringstream-lə axın əsaslı parse/formatlama ideyasını istifadə etmisiniz).

5. system_clock və steady_clock: nə saxlanıla bilər

Bu çox yayılmış səhvdir. steady_clock ölçmələr üçün ideal ola bilər (“bu qədər vaxt çəkdi”), amma o kalendara və 1970 epoxasına bağlı olmaq məcburiyyətində deyil. Onun dəyərləri yalnız proqramın işləməsi daxilində sabit ola bilər.

Sadə qayda: əgər zamanı diska yazmaq və ya başqa proqramla ötürmək istəyirsinizsə, system_clock və ya system_clock-dan alınmış ədədi format istifadə edin. steady_clock-ı isə proses daxilindəki timerlər, deadline-lar və performans ölçmələri üçün saxlayın.

6. Mini-nümunə: tapşırıqlar siyahısı olan tətbiqdə yaradılma vaxtı

Tutaq ki, artıq sadə “Tapşırıqlar siyahısı” tətbiqimiz var (onu əvvəlcə tədricən inkişaf etdirmişdik: giriş/çıxış, struct, vector, təsirli çap funksiyaları). İndi tapşırığa “nə vaxt yaradılıb” sahəsi əlavə etmək istəyirik.

Biz mükəmməl planlaşdırma sistemi yaratmırıq. Biz sadəcə bir praktik qat əlavə edirik: anı ms_since_epoch kimi saxlamaq və istifadəçiyə çap üçün format_local()-dan istifadə etmək.

Tapşırıq modeli

#include <cstdint>
#include <string>

struct Task {
    int id = 0;
    std::string title;
    std::int64_t created_ms_since_epoch = 0;
};

Diqqət yetirin: biz ədədi saxlayırıq. Bu JSON kimi serializasiya üçün rahatdır və vaxt üzrə sıralama üçün uyğundur.

Yeni tapşırığın yaradılması cari vaxtla

#include <chrono>
#include <cstdint>
#include <string>

Task make_task(int id, const std::string& title) {
    auto now = std::chrono::system_clock::now();
    return Task{ id, title, to_unix_ms(now) };
}

Burada yuxarıda verilən to_unix_ms() istifadə olunur.

Tapşırığın “insan dili” ilə çapı

#include <iostream>

void print_task(const Task& t) {
    auto tp = from_unix_ms(t.created_ms_since_epoch);
    std::cout << "#" << t.id << " " << t.title
              << " (created: " << format_local(tp) << ")\n";
    // məsələn: #1 Buy milk (created: 2026-01-16 14:07:10)
}

Burada qatların ayrılması gözə çarpır: Task-də ədəd saxlanılır, çıxışdan əvvəl isə time_point bərpa edilərək formatlanır.

7. Saxlama formatını necə seçmək

Məqsəd cədvəl yaddaşına çevrilmək deyil. Məqsəd — "his" inkişaf etdirməkdir: format tapşırığa uyğun seçilsin, yoxsa "belə alındı" prinsipi ilə deyil. Bu xüsusilə vacibdir, çünki məlumatları saxlayanda sonradan formatı dəyişmək ağrılı olur — köhnə faylları miqrasiya etməli olacaqsınız.

Saxlama Uyğundur Nümunə sahə Şərh
std::int64_t ms_since_epoch
hadisələr, loqlar, zaman üzrə sıralama, dəqiqlik
created_ms_since_epoch
praktik “default”
std::time_t
(saniyələr)
sadə tarix/vaxt, C API ilə uyğunluq
created_seconds
daha az dəqiqlik, amma daha sadə
"YYYY-MM-DD HH:MM:SS" insan oxuyacaq/edits edəcək, konfiqlər
created_text
gözlər üçün rahat, maşın emalı üçün daha pis

Layihədə artıq JSON istifadə edirsinizsə, ms_since_epoch xüsusilə xoşdur: o ədəd kimi saxlanılır, qaçırma tələb etmir və lokalaya bağlı deyil.

ISO 8601 barədə fanatizmə varmadan

Çox vaxt siz 2026-01-16T14:07:10 kimi format görəcəksiniz. Bu ISO 8601-ə oxşayır. Tam versiya adətən saat qurşağını da ehtiva edir (Z UTC üçün və ya +03:00), amma biz burada saat qurşaqlarına girmirik ki, mühazirəni elmi trillerə çevirməyək.

Praktik fikir: əgər sətir yazırsınızsa, sahə ardıcıllığını açıq edin. YYYY-MM-DD formatı yaxşıdır, çünki lüğəvi olaraq zaman üzrə də düzgün sıralanır (eyni zona və format şərti ilə).

Siz artıq YYYY-MM-DD HH:MM:SS-i put_time ilə çap etməyi bilirsiniz. İstəsəniz, boşluğun yerinə T qoymaq olar:

// format_local daxilində:
out << std::put_time(&tm, "%Y-%m-%dT%H:%M:%S"); // 2026-01-16T14:07:10

8. Tipik səhvlər

Səhv №1: vaxtı int kimi saxlamaq və vahidi adlandırmamaq.
int created = 1737040000; saniyə də, millisekund da, ümumiyyətlə “proqramın startından saniyələr” də ola bilər. Bir neçə həftədən sonra bu müəllifin nə demək istədiyi səviyyəsində sirrə çevrilir. Həll iki addımdır: vahidi adda sabitləyin (*_ms_since_epoch) və mümkün olduqda std::int64_t-dən istifadə edin, çünki millisekundlar tez böyük ədədlərə çevrilir.

Səhv №2: diska yazarkən system_clock və steady_clock-ı qarışdırmaq.
steady_clock proses daxilində ölçmələr və deadline-lar üçün lazımdır, amma “kalendari” deyil. Əgər steady_clock::now() dəyərini saxlayıb sonra onu “tarix” kimi şərh etməyə çalışsanız, gözəl fantastika əldə edəcəksiniz, amma pis proqram. Anı saxlamaq üçün system_clock və ondan alınan ədədlərdən istifadə edin.

Səhv №3: time_since_epoch().count()-ı konkret vahidə çevirmədən çap etmək.
count() cari duration-ın vahidlərində ədəd qaytarır. Əgər siz duration_cast<std::chrono::milliseconds>(...) etməmisinizsə, bunu “millisekund” olduğunu iddia edə bilməzsiniz. Düzü əvvəlcə lazımi vahidə çevirin, sonra ədədi çap edib " ms" kimi qeyd edin.

Səhv №4: std::localtime()-dan qaytarılan göstəricini götürüb onu “sonra istifadə etmək üçün” saxlamaq.
std::localtime() daxili obyektdən göstərici qaytarır və növbəti çağırışda həmin obyekt yenidən yazıla bilər. Ona görə yaxşı nümunə: dərhal std::tm tm = *std::localtime(&t); şəklində kopyalamaq. Bu təkrar formatlamalarda qəribə effektlər almaq riskini xeyli azaldır.

Səhv №5: vaxtı “hansı gəldi” sətir kimi saxlamaq və sonra onu parse etməyə çalışmaq.
Əgər bir dəfə "16/01/2026 14:07" yazmısınızsa, sonra saniyələrə ehtiyac duyub və ya formatı "01-16-2026"-a dəyişdirmisinizsə, özünüzə mini-format migrasiyası yaratmısınız. Tədris layihəsində bu sadəcə əsəbi edir, real layihədə isə geriyə uyğunluğu qırır. Əgər sətir saxlayırsınızsa, bir formatı sabitləyin və səbəbsiz dəyişdirməyin.

1
Tapşırıq
C++ SELF, səviyyə, dərs
Bağlanıb
Vaxtın bölünməsi
Vaxtın bölünməsi
1
Tapşırıq
C++ SELF, səviyyə, dərs
Bağlanıb
Dairəvi çevirici
Dairəvi çevirici
1
Tapşırıq
C++ SELF, səviyyə, dərs
Bağlanıb
Hadisələr növbəsi
Hadisələr növbəsi
1
Tapşırıq
C++ SELF, səviyyə, dərs
Bağlanıb
UTC-də jurnal
UTC-də jurnal
1
Sorğu/viktorina
, səviyyə, dərs
Əlçatan deyil
std::chrono
std::chrono
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION