IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API40/51-dars25 daqiqa
Mundarija (29)

Backend'da sana va vaqt: UTC'da saqlash, Asia/Tashkent kuni va Temporal

Qisqacha: Serverda vaqt doim UTC'da saqlanadi va uzatiladi — ISO 8601, oxirida Z: "2026-10-23T19:30:00.000Z". Mintaqa (Toshkent, UTC+5) faqat ikki joyda kerak: foydalanuvchi "24-oktabr" deganda uni UTC oralig'iga aylantirishda va matnni ko'rsatishda. vazifalar-api da ?dan=2026-10-24 — Toshkent kuni: [2026-10-23T19:00Z, 2026-10-24T19:00Z). Server jarayonining o'z mintaqasiga (TZ) hech narsa bog'lanmaydi — getDate() Toshkentda 24, UTC'da 23 qaytaradi. Temporal Node 24 da hali bayroq ortida (--harmony-temporal).

Bu darsda

  • Nega vaqt UTC'da saqlanishini va mintaqa qayerda kerakligini tushuntira olasiz.
  • Date ning serverdagi tuzoqlarini (sana-only satr, jim to'g'rilash, jarayon TZ si) bilasiz.
  • Mahalliy kun chegarasini Intl bilan UTC lahzaga aylantirasiz (DST'li mintaqada ham).
  • O'zbekcha sana formatlash va uning ICU'ga bog'liq tuzoqlarini bilasiz.
  • Temporal, date-fns, Luxon va cron mintaqasi haqida qaror qabul qila olasiz; holat 40 ni commit qilasiz.

Oldin bilishingiz kerak: Date va vaqt zonalari, Temporal API va sana kutubxonalari, Intl.DateTimeFormat, Env validatsiyasi va tipli config.

1. Nega bu kerak?

Malika yarim tundan keyin, 00:30 da vazifa qo'shdi. Ertalab ilovada "Bugungi vazifalar" ni ochdi — vazifasi yo'q. "Kechagi" ro'yxatda turibdi.

Sabab: server vazifani 2026-10-23T19:30:00.000Z deb saqlagan (to'g'ri), "bugun" filtri esa UTC sanasi bo'yicha ishlagan: UTC'da hali 23-oktabr. Malika uchun esa bu 24-oktabrning yarim tundan keyingi daqiqalari.

Boshqa ko'rinishi: server Germaniyadagi VPS'da, TZ=Europe/Berlin. Dasturchi new Date().getDate() yozdi — kod lokalda (Toshkent) bir xil, serverda boshqa natija beradi. Yoki aksincha — testlar dasturchi kompyuterida o'tadi, CI'da (UTC) yiqiladi.

Date va vaqt zonalari darsida bularni brauzerda ko'rgansiz. Serverda qo'shimcha muammo bor: server ko'p foydalanuvchiga xizmat qiladi va ularning mintaqasi har xil bo'lishi mumkin. Shuning uchun qoida qat'iy.

O'xshatish: aeroport tablosi. Hamma reyslar bitta umumiy soatda (UTC) rejalashtiriladi — uchuvchi, dispetcher, kompyuter bir-birini adashtirmasin. Yo'lovchiga esa tabloda mahalliy vaqt ko'rsatiladi. Rejalashtirish va ko'rsatish — ikki xil ish.

2. Qoida: UTC'da saqlash, chegarada aylantirish

2.1 Uch joy

Joy Shakl vazifalar-api da
Saqlash (ombor, baza) UTC lahza yaratildi: "2026-10-23T19:30:00.000Z"
Uzatish (JSON javob) ISO 8601, Z bilan o'sha satr
Foydalanuvchi kiritishi / ko'rsatish Mahalliy kun yoki matn ?dan=2026-10-24 (APP_TZ), frontend Intl

Ikki atama:

  • Lahza (instant) — vaqt o'qidagi bitta nuqta. Butun dunyoda bir xil: 19:30Z Toshkentda 00:30, Londonda 20:30 — lekin lahza bitta.
  • Kalendar kuni — "24-oktabr". U lahza emas: Toshkentda 24-oktabr UTC bo'yicha 23-oktabr 19:00 da boshlanadi, Nyu-Yorkda — 04:00 da. Kun mintaqasiz ma'nosiz.

Frontend'ga lahzani (…Z) yuboring, matn emas. Brauzer foydalanuvchining o'z mintaqasida formatlaydi (Intl darsi).

2.2 Kanon: ombordagi sanalar

Kanon ombori (xotira-ombor.ts) vazifa yaratishda this.#soat().toISOString() ishlatadi — toISOString doim UTC va doim Z bilan (Date va vaqt zonalari darsidagi qoida). Holat 40 dagi shartnoma testlari (pastda, «API qadami») har omborda buni tekshiradi:

ts
assert.match(v.yaratildi,
  /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);

26-qismda Postgres'da ustun turi timestamptz bo'ladi — u ham ichida UTC saqlaydi. Shartnoma testi Postgres ombori ham shu shaklni qaytarishini talab qiladi.

3. Serverda Date tuzoqlari

3.1 Kanon o'lchovi

t40-sana.txt (Node 24.21.0, tizim mintaqasi — Toshkent). Har qatorda chapda — kod, o'ngda — natija. Avval birinchi ikki qatorni solishtiring: kirish satrlari deyarli bir xil, natijalar esa 5 soat farq qiladi:

text
new Date("2026-10-24")            → 2026-10-24T00:00:00.000Z (sana-only satr — UTC deb o'qiladi)
new Date("2026-10-24T00:00")      → 2026-10-23T19:00:00.000Z (vaqtli, Z siz — MAHALLIY: TZ=tizim, Asia/Tashkent)
new Date(2026, 9, 24)             → 2026-10-23T19:00:00.000Z (oy 0 dan; mahalliy)
new Date("2026-02-30")            → Mon Mar 02 2026 05:00:00 GMT+0500 (Uzbekistan Standard Time); Date.UTC(2026,1,30) → 2026-03-02T00:00:00.000Z (jim to'g'rilaydi)
UTC lahza 2026-10-23T19:30:00.000Z → getDate() 24 (jarayon mintaqasi), getUTCDate() 23
TZ=UTC              getDate/getHours: 23 19 Fri Oct 23 2026 19:30:00 GMT+0000 (Coordinated Universal Time)
TZ=Asia/Tashkent    getDate/getHours: 24 0 Sat Oct 24 2026 00:30:00 GMT+0500 (Uzbekistan Standard Time)
TZ=America/New_York getDate/getHours: 23 15 Fri Oct 23 2026 15:30:00 GMT-0400 (Eastern Daylight Time)

To'rt saboq:

  1. Bir xil ko'rinadigan ikki satr — "2026-10-24" (UTC) va "2026-10-24T00:00" (mahalliy) — 5 soat farq. Serverda mahalliy = serverning TZ si, ya'ni tasodif.
  2. 2026-02-30 xato emas — Date uni jim 2-martga aylantiradi. Foydalanuvchi kiritgan sanani shunchaki new Date() ga berish — tekshiruv emas.
  3. getDate(), getHours() — jarayon mintaqasida. Bitta kod, uch xil TZ — uch xil javob. Serverda faqat getUTC… yoki aniq timeZone bilan Intl.
  4. TZ muhit o'zgaruvchisi — Node jarayonining mintaqasini o'zgartiradi. Ko'p server va konteynerlarda u odatda UTC; lekin bunga tayanib kod yozmang.

3.2 Haqiqiy sanani tekshirish

Kanon ?dan= uchun shunday tekshiradi (sxema.ts):

js
function haqiqiySana(x) {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(x)) return false;
  const t = Date.parse(`${x}T00:00:00Z`);
  return !Number.isNaN(t) && new Date(t).toISOString().startsWith(x);
}

for (const x of ["2026-10-24", "2026-02-30", "2028-02-29",
  "2026-2-5", "2026-13-01"]) {
  console.log(x, haqiqiySana(x));
}

Konsolda:

text
2026-10-24 true
2026-02-30 false
2028-02-29 true
2026-2-5 false
2026-13-01 false

Hiyla: sanani UTC yarim tuni sifatida o'qib, qayta matnga aylantiramiz. 2026-02-30 → 2026-03-02… — boshlanishi mos kelmadi, demak "jim to'g'rilangan". 2028-02-29 — kabisa yili, haqiqiy. T00:00:00Z — Z bilan: jarayon mintaqasi aralashmasin. Kanon jonli chiqishi:

text
$ curl $A/v1/vazifalar?dan=2026-02-30
{"type":"https://vazifalar-api.example/xatolar/parametr","title":"Parametr noto'g'ri","status":400,"detail":"dan — YYYY-MM-DD (haqiqiy sana)",…}

4. Mahalliy kun → UTC oraliq

4.1 Muammo

?dan=2026-10-24&gacha=2026-10-24 — "Toshkentda 24-oktabr kuni yaratilgan vazifalar". Bizga UTC oraliq kerak: [24-okt 00:00 Toshkent, 25-okt 00:00 Toshkent) = [23-okt 19:00Z, 24-okt 19:00Z).

Toshkent uchun oddiy: har doim UTC+5 (IANA bazasiga ko'ra, O'zbekiston yozgi vaqtni oxirgi marta 1991 yilda ishlatgan, 1992 dan beri — yo'q). Lekin APP_TZ istalgan IANA mintaqa bo'lishi mumkin — Nyu-Yorkda yozda −4, qishda −5. Siljishni qo'lda yozish ("+5") — keyinchalik xato.

4.2 Kanon: src/umumiy/sana.ts

Temporal yo'q, shuning uchun Intl.DateTimeFormat ning formatToParts imkoniyati bilan. Kanon kodi (JS ko'rinishida, brauzerda ham ishlaydi):

js
function siljish(tz, lahza) {
  const q = new Intl.DateTimeFormat("en-US", {
    timeZone: tz, hourCycle: "h23",
    year: "numeric", month: "2-digit", day: "2-digit",
    hour: "2-digit", minute: "2-digit", second: "2-digit",
  }).formatToParts(lahza);
  const ol = (tur) => Number(q.find((p) => p.type === tur)?.value);
  const mahalliy = Date.UTC(ol("year"), ol("month") - 1, ol("day"),
    ol("hour"), ol("minute"), ol("second"));
  const butun = Math.floor(lahza.getTime() / 1000) * 1000;
  return Math.round((mahalliy - butun) / 60_000);
}

function kunBoshiUtc(sana, tz) {
  const [y, m, d] = sana.split("-").map(Number);
  const taxmin = Date.UTC(y, m - 1, d);
  const birinchi = taxmin - siljish(tz, new Date(taxmin)) * 60_000;
  const s = siljish(tz, new Date(birinchi));
  return new Date(taxmin - s * 60_000).toISOString();
}

console.log(siljish("Asia/Tashkent", new Date("2026-10-24T00:00Z")));
console.log(kunBoshiUtc("2026-10-24", "Asia/Tashkent"));
console.log(kunBoshiUtc("2026-07-01", "America/New_York"));
console.log(kunBoshiUtc("2026-12-01", "America/New_York"));

Konsolda:

text
300
2026-10-23T19:00:00.000Z
2026-07-01T04:00:00.000Z
2026-12-01T05:00:00.000Z

Qanday ishlaydi:

  • siljish — "shu lahzada mintaqadagi soat necha?" ni qismlarga bo'lib so'raydi, ularni go'yo UTC deb Date.UTC ga beradi va haqiqiy lahzadan ayiradi. Farq — siljish daqiqalarda (Toshkent: +300).
  • kunBoshiUtc — ikki marta hisoblaydi. Birinchi taxmin "UTC yarim tun"dagi siljish bilan. DST o'tadigan kunda siljish yarim tunga yaqin o'zgarishi mumkin — shuning uchun topilgan nuqtadagi siljish bilan qayta hisoblanadi.
  • Natija: Nyu-Yorkda yozda kun 04:00Z da, qishda 05:00Z da boshlanadi — qo'lda yozilgan "+5" yoki "−4" bu farqni ko'rmaydi.

?gacha= uchun keyingi kun boshi olinadi va oraliq ochiq: yaratildi < gacha. Shunda 23:59:59.999 ham kiradi, keyingi kunning 00:00 i — yo'q. Kanon sorov.ts:

ts
if (q.dan !== undefined) filtr.yaratildiDan = kunBoshiUtc(q.dan, tz);
if (q.gacha !== undefined)
  filtr.yaratildiGacha = kunBoshiUtc(keyingiKun(q.gacha), tz);

ISO satrlarni solishtirish (v.yaratildi >= filtr.yaratildiDan) to'g'ri ishlaydi. Satrlar lug'atdagi kabi belgima-belgi solishtiriladi (leksik tartib): avval yil, keyin oy, kun, soat. Ikkalasi bir xil formatda va UTC'da bo'lgani uchun leksik tartib vaqt tartibiga teng.

flowchart LR
  A["?dan=2026-10-24<br/>(Toshkent kuni)"] --> B["kunBoshiUtc<br/>APP_TZ bilan"]
  B --> C["23-okt 19:00Z"]
  D["?gacha=2026-10-24"] --> E["keyingiKun → 25"]
  E --> F["24-okt 19:00Z"]
  C --> G["yaratildi ≥ dan<br/>va &lt; gacha"]
  F --> G

Diagrammada foydalanuvchi bilan gaplashadigan qism (chapda) — mahalliy kun, ombor bilan gaplashadigan qism (o'ngda) — faqat UTC.

4.3 Kanon testi — Malikaning holati

test/sana.test.ts soxta soat bilan ikki vazifa yaratadi: 18:30Z (Toshkentda 23-okt 23:30) va 19:30Z (Toshkentda 24-okt 00:30):

ts
assert.deepEqual(await ol("dan=2026-10-24&gacha=2026-10-24"),
  ["yarim tundan keyin"]);
assert.deepEqual(await ol("gacha=2026-10-23"), ["kech"]);
assert.equal((await fetch(`${asos}/v1/vazifalar?dan=2026-02-30`))
  .status, 400);

UTC sanasi bo'yicha ikkalasi ham 23-oktabr. Toshkent kuni bo'yicha — har xil. Test «Nega bu kerak?» bo'limidagi xatoni qaytarilmas qiladi.

Tekshirib ko'ring: Nega ?dan= ni APP_TZ bo'yicha emas, har foydalanuvchining o'z mintaqasi bo'yicha hisoblash yaxshiroq bo'lardi? Kanon nega bunday qilmadi?

Javob

Haqiqiy ko'p mintaqali ilovada har foydalanuvchining "bugun" i o'z mintaqasida. Buning uchun foydalanuvchi mintaqasini bilish kerak (profil sozlamasi yoki so'rov parametri ?tz=). vazifalar-api da foydalanuvchilar hali yo'q (auth — 28-qism) va auditoriya O'zbekistonda — bitta APP_TZ yetarli. Kanon buni config'da qoldirdi: kerak bo'lsa keyin so'rov darajasiga tushiriladi.

5. O'zbekcha formatlash va ICU

5.1 Kanon o'lchovi

Bitta UTC lahza, timeZone: "Asia/Tashkent", dateStyle: "full", timeStyle: "short" (t40-sana.txt):

text
uz          shanba, 24-oktabr, 2026, 00:30
uz-Latn-UZ  shanba, 24-oktabr, 2026, 00:30
uz-Cyrl-UZ  шанба, 24 октябр, 2026 00:30
ru-RU       суббота, 24 октября 2026 г. в 00:30
en-GB       Saturday, 24 October 2026 at 00:30
uz, month long: 24-oktabr; resolvedOptions: "uz"
RelativeTimeFormat uz: kecha, 3 soatdan keyin
ICU: 78.3, CLDR 48.0, tz 2026c

Oxirgi qator — eng muhimi. Formatlash natijasini Node emas, ICU kutubxonasi va CLDR (til ma'lumotlari bazasi) belgilaydi. Ular har Node (va har brauzer) versiyasida yangilanadi. Kecha 24-oktabr, ertaga — boshqa tinish belgisi. Bu matn brauzerda tekshirilmagan — Chrome o'z ICU'siga ega.

Uch qoida:

  • Formatlangan matnni saqlamang va tahlil qilmang — faqat ko'rsating.
  • Testda formatlangan matnni aynan solishtirish — mo'rt; Node yangilansa yiqiladi.
  • Serverda formatlash kerak bo'lsa (email, PDF, Telegram xabari) — timeZone ni doim aniq bering, aks holda jarayon TZ si ishlatiladi.

5.2 tz ma'lumotlar bazasi

tz 2026c — IANA mintaqalar bazasining versiyasi. Davlatlar soat qoidalarini o'zgartirib turadi; Node (ICU orqali) bu bazani o'zi bilan olib yuradi. Eski Node — eski qoidalar. Kanon APP_TZ ni Intl.supportedValuesOf("timeZone") bilan tekshiradi — mavjud bo'lmagan mintaqa (Toshkent) config xatosi beradi (tipli config darsi).

6. Temporal va kutubxonalar

6.1 Temporal Node 24 da

Temporal darsida uni brauzerda ko'rgansiz. Node 24 da (t40):

text
typeof Temporal: undefined
node --harmony-temporal: exit 0 object 2026-10-23T19:30:00Z
node --js-temporal: exit 9 …node.exe: bad option: --js-temporal

Sukut bo'yicha yo'q, V8 ning --harmony-temporal bayrog'i bilan — bor. Kanon tajribasidagi qatorni o'zimiz ham takrorlaymiz (blok Node'da bayroq bilan ishlaydi):

js
// node --harmony-temporal
const z = Temporal.ZonedDateTime.from(
  "2026-10-24T00:30[Asia/Tashkent]");
console.log(z.toInstant().toString());
const kun = Temporal.PlainDate.from("2026-10-24");
const bosh = kun.toZonedDateTime("Asia/Tashkent").toInstant();
console.log(bosh.toString());

Konsolda:

text
2026-10-23T19:30:00Z
2026-10-23T19:00:00Z

Ikkinchi natija — kunBoshiUtc ning bitta qatorli ekvivalenti, DST hisobi bilan. harmony bayroqlari — V8 ning tajriba imkoniyatlari; production'da ishlatilmaydi. Qaror (kanon): Temporal ishlatilmadi, Intl + Date.UTC — 20 qator. Temporal barqaror bo'lgach, sana.ts shunga almashadi.

6.2 Kutubxonalar

Kutubxona Mintaqa bilan ishlash Qachon
date-fns + @date-fns/tz (v4) TZDate Funksional uslub, tree-shaking
Day.js + utc, timezone plaginlari plagin Kichik hajm, Moment.js'ga o'xshash API
Luxon ichida (DateTime.setZone) Mintaqa ko'p ishlatiladigan ilovalar
Moment.js moment-timezone Eski — 2020 dan "texnik xizmat rejimi", yangi loyihaga yo'q

Kanon hech birini o'rnatmadi: vazifalar-api da ikki funksiya kerak edi. Ular haqida batafsil — Temporal va sana kutubxonalari darsida. Ko'p sana amallari (oy qo'shish, "2 hafta oldin", ish kunlari) kerak bo'lsa — kutubxona arziydi.

7. Cron va mintaqa

"Har kuni ertalab 09:00 da kunlik hisobot yubor". Kimning 09:00 i? Server UTC'da bo'lsa — 0 9 * * * Toshkentda 14:00 da ishlaydi.

Ikki to'g'ri yo'l:

  • Cron ifodani UTC'da yozish va izohlash: 0 4 * * * # 09:00 Toshkent (UTC+5). Toshkentda DST yo'q — bu barqaror.
  • Rejalashtiruvchiga mintaqani berish: ko'p kutubxonalarda timezone: "Asia/Tashkent" opsiyasi bor. DST'li mintaqada bu yagona to'g'ri yo'l — aks holda yilda ikki marta vazifa bir soat siljiydi yoki ikki marta ishlaydi.

Rejali vazifalar to'liq — 29-qismda va Linux cron'i — 33-qismda.

8. API qadami: sana va holat 40 commit'i (holat 40, 6-qism)

Holat 40 olti darsning yig'indisi. Bugungi qism — sana:

Fayl O'zgarish
src/umumiy/sana.ts Yangi: siljish, kunBoshiUtc, keyingiKun
src/vazifalar/sxema.ts dan, gacha — haqiqiySana bilan
src/vazifalar/sorov.ts ?dan/?gacha → Filtr.yaratildiDan/Gacha (UTC ISO)
src/config.ts APP_TZ (IANA, sukut Asia/Tashkent)
test/sana.test.ts 3 test: Toshkent, Nyu-York DST, API filtri

Holat 40 ning boshqa qismlari oldingi darslarda qo'shildi: .env.example va skriptlar, SAQLASH=fayl (FaylOmbori), shartnoma testlari va tartib — env darsida; config.ts — tipli config darsida. Eslatma sifatida:

  • SAQLASH=fayl — src/vazifalar/fayl-ombor.ts: FaylOmbori vazifalarni JSON faylda saqlaydi. Har yozuvdan keyin butun holat atomik yoziladi — .<pid>.tmp + rename, mode 0o600 (Xavfsiz fayl operatsiyalari darsidagi usul). Javob diskka yozilgandan keyin qaytadi; buzilgan fayl — xato (jim bo'sh ro'yxat emas). Tanlov — src/ishga-tushir.ts dagi omborYarat(config): 26-qism shu switch ga postgres ni qo'shadi.
  • Shartnoma testlari — test/ombor-shartnoma.ts: omborShartnomasi(nom, yarat) — 5 test, har ombor (xotira, fayl, keyin Postgres) bir xil testdan o'tadi.
  • tartib maydoni va MAKS_MATN = 100 — frontend «Tartiblash» (20-qism) tartibni PUT /v1/vazifalar massiv tartibi bilan yuboradi; server tartib = indeks + 1 yozadi. Matn chegarasi 200 dan 100 ga — frontend va 26-qism sxemasi bilan bir xil.

Kanon fayllari to'liq (holat 40; qatorlar telefon uchun Prettier bilan bo'lindi, kod aynan). src/umumiy/sana.ts:

ts
// sana.ts — saqlash UTC'da, foydalanuvchi esa "24-oktabr" deganda
// Toshkent kunini nazarda tutadi.
// Mahalliy kun chegarasi → UTC lahza (Temporal Node 24 da yo'q — Intl
// bilan)

/**
 * tz mintaqasining berilgan UTC lahzadagi siljishi (daqiqa):
 * Asia/Tashkent → +300
 */
export function siljish(tz: string, lahza: Date): number {
  const qismlar = new Intl.DateTimeFormat("en-US", {
    timeZone: tz,
    hourCycle: "h23",
    year: "numeric",
    month: "2-digit",
    day: "2-digit",
    hour: "2-digit",
    minute: "2-digit",
    second: "2-digit",
  }).formatToParts(lahza);
  const q = (tur: Intl.DateTimeFormatPartTypes): number =>
    Number(qismlar.find((p) => p.type === tur)?.value);
  const mahalliy = Date.UTC(
    q("year"),
    q("month") - 1,
    q("day"),
    q("hour"),
    q("minute"),
    q("second"),
  );
  return Math.round(
    (mahalliy - Math.floor(lahza.getTime() / 1000) * 1000) / 60_000,
  );
}

/**
 * "2026-10-24" (tz bo'yicha kun) → shu kun boshlanishining UTC ISO
 * lahzasi: Toshkent → "2026-10-23T19:00:00.000Z"
 */
export function kunBoshiUtc(sana: string, tz: string): string {
  const [y, m, d] = sana.split("-").map(Number);
  const taxmin = Date.UTC(y ?? 0, (m ?? 1) - 1, d ?? 1);
  // DST bo'lgan mintaqalarda siljish kunga qarab o'zgaradi — ikki
  // marta hisoblaymiz
  const birinchi = taxmin - siljish(tz, new Date(taxmin)) * 60_000;
  return new Date(
    taxmin - siljish(tz, new Date(birinchi)) * 60_000,
  ).toISOString();
}

/** Keyingi kun (mahalliy kalendar bo'yicha) */
export function keyingiKun(sana: string): string {
  const [y, m, d] = sana.split("-").map(Number);
  return new Date(Date.UTC(y ?? 0, (m ?? 1) - 1, (d ?? 1) + 1))
    .toISOString()
    .slice(0, 10);
}

src/vazifalar/sorov.ts — query → ombor so'rovi (dan/gacha shu yerda UTC'ga aylanadi):

ts
// sorov.ts — tekshirilgan ro'yxat query'sini (sxema.ts → RoyxatQuery)
// ombor so'roviga aylantirish: kursorni ochish, imzo, proyeksiya
// Konvensiya:
// ?bajarildi=true&teg=uy&teg=ish&q=non
// &sort=-yaratildi,matn&fields=id,matn&limit=20&offset=40
// | &cursor=...&jami=true
import { ParametrXatosi } from "../umumiy/xatolar.ts";
import { kursorOqi } from "../umumiy/kursor.ts";
import { keyingiKun, kunBoshiUtc } from "../umumiy/sana.ts";
import type { Filtr, RoyxatSorovi } from "./ombor.ts";
import type { Maydon, RoyxatQuery } from "./sxema.ts";
import type { Vazifa } from "./vazifa.ts";

export type TekshirilganSorov = {
  sorov: RoyxatSorovi;
  maydonlar?: Maydon[];
  imzo: string;
};

/**
 * @param tz foydalanuvchi kuni qaysi mintaqada (config APP_TZ) —
 * saqlangan vaqt esa UTC
 */
export function royxatSorovi(
  q: RoyxatQuery,
  tz = "Asia/Tashkent",
): TekshirilganSorov {
  const filtr: Filtr = {};
  if (q.dan !== undefined)
    filtr.yaratildiDan = kunBoshiUtc(q.dan, tz);
  if (q.gacha !== undefined)
    filtr.yaratildiGacha = kunBoshiUtc(keyingiKun(q.gacha), tz);
  if (q.bajarildi !== undefined) filtr.bajarildi = q.bajarildi;
  if (q.teg !== undefined) filtr.teglar = q.teg;
  if (q.q !== undefined) filtr.qidiruv = q.q;
  const saralash = q.sort ?? [];
  // Imzo — kursor boshqa filtr/saralash bilan qayta ishlatilmasin
  const imzo = JSON.stringify([saralash, filtr]);
  let sahifa: RoyxatSorovi["sahifa"];
  if (q.cursor !== undefined) {
    const keyin = kursorOqi(q.cursor, imzo);
    if (keyin === undefined)
      throw new ParametrXatosi(
        "cursor yaroqsiz yoki boshqa saralash/filtr uchun",
        [{ joy: "query.cursor", xabar: "yaroqsiz" }],
      );
    sahifa = { tur: "kursor", keyin };
  } else {
    // Parametrsiz — kursor rejimi (birinchi sahifa); offset faqat
    // aniq so'ralganda
    sahifa =
      q.offset === undefined
        ? { tur: "kursor" }
        : { tur: "offset", offset: q.offset };
  }
  const sorov: RoyxatSorovi = {
    filtr,
    saralash,
    limit: q.limit,
    sahifa,
  };
  if (q.jami !== undefined) sorov.jami = q.jami;
  return q.fields === undefined
    ? { sorov, imzo }
    : { sorov, maydonlar: q.fields, imzo };
}

/** Sparse fieldsets: faqat so'ralgan maydonlar */
export function proyeksiya(
  v: Vazifa,
  maydonlar: readonly Maydon[] | undefined,
): Partial<Vazifa> {
  if (maydonlar === undefined) return v;
  return Object.fromEntries(maydonlar.map((m) => [m, v[m]]));
}

Qolgan o'zgarishlar — farq (diff) ko'rinishida. Farq Prettier bilan bo'lingan holat 34 va holat 40 fayllari orasida olingan (kanon fayllarida qatorlar uzun — faqat format farqi). sxema.ts — haqiqiySana, dan/gacha, tartib maydoni va MAKS_MATN = 100:

diff
--- a/sxema.ts
+++ b/sxema.ts
@@ -6,5 +6,7 @@
 import type { Saralash, SaralashMaydoni } from "./ombor.ts";
 
-export const MAKS_MATN = 200;
+// 22-qism frontend (Vazifa.MAKS_UZUNLIK) va 26-qism sxemasi (CHECK
+// 1–100) bilan bir xil
+export const MAKS_MATN = 100;
 export const MAKS_TOPLAM = 1000;
 export const SUKUT_LIMIT = 20;
@@ -96,4 +98,19 @@
     });
 const TEG = /^[\p{L}\p{N}_'‘’]{1,50}$/u;
+/**
+ * YYYY-MM-DD va haqiqiy kalendar sanasi (2026-02-30 — yo'q: Date uni
+ * 03-02 ga "to'g'rilab" yuboradi)
+ */
+function haqiqiySana(x: string): boolean {
+  if (!/^\d{4}-\d{2}-\d{2}$/.test(x)) return false;
+  const t = Date.parse(`${x}T00:00:00Z`);
+  return (
+    !Number.isNaN(t) && new Date(t).toISOString().startsWith(x)
+  );
+}
+const Sana = (nom: string) =>
+  Bitta(nom).refine(haqiqiySana, {
+    error: `${nom} — YYYY-MM-DD (haqiqiy sana)`,
+  });
 const MAYDONLAR = [
   "id",
@@ -101,4 +118,5 @@
   "bajarildi",
   "teglar",
+  "tartib",
   "yaratildi",
   "yangilandi",
@@ -189,4 +207,8 @@
       cursor: Bitta("cursor").max(1000).optional(),
       jami: Mantiqiy("jami").optional(),
+      // Mahalliy kalendar kuni (APP_TZ, sukut Asia/Tashkent):
+      // ?dan=2026-10-24&gacha=2026-10-24 — ikkalasi ham kiradi
+      dan: Sana("dan").optional(),
+      gacha: Sana("gacha").optional(),
     },
     {

marshrut.ts — mintaqa routerga parametr bo'lib keladi:

diff
--- a/marshrut.ts
+++ b/marshrut.ts
@@ -51,7 +51,10 @@
   return `${req.baseUrl}?${p.toString()}`;
 }
 
-export function vazifalarRouteri(xizmat: VazifaXizmati): Router {
+export function vazifalarRouteri(
+  xizmat: VazifaXizmati,
+  { tz = "Asia/Tashkent" } = {},
+): Router {
   const r = Router();
 
   r.route("/")
@@ -61,7 +64,10 @@
       tekshirib(
         { query: RoyxatQuerySxemasi },
         async ({ query }, req, res) => {
-          const { sorov, maydonlar, imzo } = royxatSorovi(query);
+          const { sorov, maydonlar, imzo } = royxatSorovi(
+            query,
+            tz,
+          );
           const natija = await xizmat.royxat(sorov);
           const sahifa: Record<string, unknown> = {
             limit: sorov.limit,

test/xato.test.ts — matn chegarasi 100:

diff
--- a/xato.test.ts
+++ b/xato.test.ts
@@ -49,13 +49,13 @@
     type: "https://vazifalar-api.example/xatolar/validatsiya",
     title: "So'rov ma'lumoti noto'g'ri",
     status: 422,
-    detail: "matn 1–200 belgi bo'lishi kerak",
+    detail: "matn 1–100 belgi bo'lishi kerak",
     instance: "/v1/vazifalar",
     kod: "validatsiya",
     xatolar: [
       {
         joy: "body.matn",
-        xabar: "matn 1–200 belgi bo'lishi kerak",
+        xabar: "matn 1–100 belgi bo'lishi kerak",
       },
     ],
     sorovId: r.headers.get("x-request-id"),

test/sana.test.ts:

ts
import { after, before, test } from "node:test";
import assert from "node:assert/strict";
import {
  keyingiKun,
  kunBoshiUtc,
  siljish,
} from "../src/umumiy/sana.ts";
import { ishgaTushir } from "../src/ishga-tushir.ts";
import { yop } from "../src/yopish.ts";
import { XotiraOmbori } from "../src/vazifalar/xotira-ombor.ts";

test("Toshkent kuni → UTC: 24-oktabr 00:00 (UTC+5) = 23-oktabr 19:00Z", () => {
  assert.equal(
    siljish("Asia/Tashkent", new Date("2026-10-24T00:00:00Z")),
    300,
  );
  assert.equal(
    kunBoshiUtc("2026-10-24", "Asia/Tashkent"),
    "2026-10-23T19:00:00.000Z",
  );
  assert.equal(
    kunBoshiUtc("2026-10-24", "UTC"),
    "2026-10-24T00:00:00.000Z",
  );
  assert.equal(keyingiKun("2026-12-31"), "2027-01-01");
});

test("DST bor mintaqa: Nyu-York yoz (UTC-4) va qish (UTC-5)", () => {
  assert.equal(
    kunBoshiUtc("2026-07-01", "America/New_York"),
    "2026-07-01T04:00:00.000Z",
  );
  assert.equal(
    kunBoshiUtc("2026-12-01", "America/New_York"),
    "2026-12-01T05:00:00.000Z",
  );
});

let asos = "";
let toxta: () => Promise<boolean>;
before(async () => {
  // Soxta soat: UTC 23-okt 18:30 (Toshkentda 23:30) va 19:30
  // (Toshkentda 24-okt 00:30)
  const vaqtlar = [
    "2026-10-23T18:30:00.000Z",
    "2026-10-23T19:30:00.000Z",
  ];
  const ombor = new XotiraOmbori(
    () => new Date(vaqtlar.shift() ?? "2026-10-25T00:00:00.000Z"),
  );
  await ombor.yarat({ matn: "kech" });
  await ombor.yarat({ matn: "yarim tundan keyin" });
  const { server, manzil } = await ishgaTushir({
    port: 0,
    bogliqliklar: { ombor, tz: "Asia/Tashkent" },
  });
  asos = manzil;
  toxta = () => yop(server, 1000);
});
after(() => toxta());

test("?dan=&gacha= — Toshkent kalendar kuni bo'yicha (UTC sanasi bo'yicha emas)", async () => {
  const ol = async (q: string) =>
    (
      (await (
        await fetch(`${asos}/v1/vazifalar?${q}&fields=matn`)
      ).json()) as { vazifalar: { matn: string }[] }
    ).vazifalar.map((v) => v.matn);
  assert.deepEqual(await ol("dan=2026-10-24&gacha=2026-10-24"), [
    "yarim tundan keyin",
  ]);
  assert.deepEqual(await ol("gacha=2026-10-23"), ["kech"]);
  assert.equal(
    (await fetch(`${asos}/v1/vazifalar?dan=2026-02-30`)).status,
    400,
  );
});

src/ishga-tushir.ts (omborYarat bilan):

ts
// ishga-tushir.ts — ombor tanlash (config bo'yicha), http server
// (Express ilovasi ishlovchi sifatida), timeout'lar va tinglash
import { createServer } from "node:http";
import type { Server } from "node:http";
import type { AddressInfo } from "node:net";
import { ilovaYarat } from "./ilova.ts";
import type { Bogliqliklar } from "./ilova.ts";
import type { Config } from "./config.ts";
import { XotiraOmbori } from "./vazifalar/xotira-ombor.ts";
import { FaylOmbori } from "./vazifalar/fayl-ombor.ts";
import type { VazifaOmbori } from "./vazifalar/ombor.ts";

export type Ishlayotgan = { server: Server; manzil: string };

/**
 * SAQLASH=xotira|fayl — 26-qismda shu yerga "postgres" (pg Pool)
 * qo'shiladi; qolgan kod VazifaOmbori interfeysini biladi, xolos
 */
export async function omborYarat(
  config: Pick<Config, "SAQLASH" | "FAYL_YOLI">,
): Promise<VazifaOmbori> {
  switch (config.SAQLASH) {
    case "xotira":
      return new XotiraOmbori();
    case "fayl":
      return FaylOmbori.och(config.FAYL_YOLI);
  }
}

type Sozlama = {
  port?: number;
  host?: string;
  bogliqliklar?: Bogliqliklar;
};

export async function ishgaTushir({
  port = 0,
  host = "127.0.0.1",
  bogliqliklar = { ombor: new XotiraOmbori() },
}: Sozlama = {}): Promise<Ishlayotgan> {
  // app.listen() ham shuni qiladi; createServer — timeout'larni
  // berish va server obyektini olish uchun
  const server = createServer(
    {
      // Load balancer (nginx, ALB) bo'sh ulanishni ~60 s ushlaydi —
      // Node undan KEYIN yopsin
      keepAliveTimeout: 65_000,
      // Slowloris: sarlavhalar 10 s ichida to'liq kelmasa — 408
      headersTimeout: 10_000,
      requestTimeout: 30_000,
    },
    ilovaYarat(bogliqliklar),
  );
  await new Promise<void>((resolve, reject) => {
    server.once("error", reject); // EADDRINUSE va h.k.
    server.listen(port, host, () => {
      server.off("error", reject);
      resolve();
    });
  });
  const { port: haqiqiy } = server.address() as AddressInfo;
  return { server, manzil: `http://${host}:${String(haqiqiy)}` };
}

Kanon jonli chiqishlari (tekshiruv-40.txt). Uch so'rovda vazifalar massiviga qarang — nechta vazifa qaytdi:

text
$ curl $A/v1/vazifalar?dan=2026-10-24&fields=matn,yaratildi
{"versiya":1,"vazifalar":[{"id":1,"matn":"Bugungi vazifa #uy","yaratildi":"2026-10-24T05:55:46.307Z"}],"sahifa":{"limit":20,"keyingi":null}}
$ curl $A/v1/vazifalar?gacha=2026-01-01&fields=matn
{"versiya":1,"vazifalar":[],"sahifa":{"limit":20,"keyingi":null}}
--- SAQLASH=fayl: ikki marta ishga tushirish
$ curl 'http://127.0.0.1:4199/v1/vazifalar?fields=matn' (qayta ishga tushgandan keyin)
{"versiya":1,"vazifalar":[{"id":1,"matn":"diskda saqlanadi"}],"sahifa":{"limit":20,"keyingi":null}}
fayl: 208 bayt

Nima bo'ldi? Bugun (24-oktabr, Toshkent) yaratilgan vazifa ?dan=2026-10-24 bilan topildi; ?gacha=2026-01-01 bilan — bo'sh ro'yxat, chunki 1-yanvar va undan oldin hech narsa yaratilmagan. Uchinchi so'rov SAQLASH=fayl ni tekshiradi: server o'chirilib qayta yoqilgandan keyin ham vazifa joyida — u diskdagi 208 baytli faylda saqlangan.

Tekshiring:

bash
npm run check

Kanon natijasi: tip 0 (3.1 s), lint 0 (6.1 s), testlar 54/54. Yangi 18 test: config 2, sana 3, shartnoma 5 × 2, FaylOmbori 3. Prod bog'liqlik 4 — o'zgarmadi; sovuq start 540 ms (mediana, mashina yuklangan). Yuk (autocannon, bitta mashina, bitta o'lchov — faqat holatlarni solishtirish uchun): ?limit=100 1 660, filtrli 3 196, /v1/vazifalar/5 6 448, 404 5 116 req/s.

Commit (kanon):

bash
git add -A
git commit -m "Config: Zod bilan tipli va muzlatilgan, fail-fast (exit 78); .env.example; SAQLASH=fayl (atomik JSON); ?dan/?gacha Toshkent kuni; tartib; matn 100"

9. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
Yarim tundan keyingi vazifa "kecha" da Filtr UTC sanasi bo'yicha kunBoshiUtc(sana, APP_TZ)
Lokalda o'tadi, CI'da yiqiladi getDate() — jarayon TZ si getUTC… yoki Intl + timeZone
2026-02-30 qabul qilindi Date jim to'g'rilaydi toISOString().startsWith(x) tekshiruvi
"2026-10-24T00:00" 5 soat siljidi Z siz satr — mahalliy Doim Z yoki aniq siljish
Hisobot 14:00 da keladi Cron UTC'da UTC'da yozing yoki timezone opsiyasi
Node yangilangach test yiqildi ICU/CLDR o'zgardi Formatlangan matnni aynan solishtirmang
Nyu-York filtri yilda ikki marta xato Siljish qo'lda (−5) IANA mintaqa + Intl / Temporal

10. Mashqlar

Yechimlar kurs/mashqlar/25/40-sana-vaqt/ da.

1-mashq (oson): UTC'da nima?

Toshkent (UTC+5, DST yo'q):

  • Toshkentda 24-okt 00:30 — UTC: 2026-10-23T:00Z
  • Toshkentda 24-okt kuni boshi — UTC: 2026-10-T19:00:00Z
  • new Date("2026-10-24").toISOString() — 2026-10-24T:00.000Z
  • ?gacha=2026-10-24 filtri yaratildi < qaysi lahza? 2026-10-T19:00:00.000Z
Yechim
  • 19:30 — 00:30 − 5 soat, oldingi kun.
  • 23 — Toshkent kuni UTC'da 5 soat oldin boshlanadi.
  • 00:00 — sana-only satr UTC deb o'qiladi.
  • 24 — keyingi kun (25-okt) boshi Toshkentda = 24-okt 19:00Z; oraliq ochiq (<).

2-mashq (o'rta): kun oralig'i

kunOraligi(dan, gacha, tz) yozing: darsdagi kunBoshiUtc bilan { dan, gacha } (UTC ISO) qaytarsin; gacha — keyingi kun boshi. dan > gacha bo'lsa — RangeError("dan gacha'dan keyin"). Sinang: Toshkent 24–24, Nyu-York 2026-03-08 (DST boshlangan kun — kun 23 soat).

Yechim
js
function siljish(tz, lahza) {
  const q = new Intl.DateTimeFormat("en-US", {
    timeZone: tz, hourCycle: "h23",
    year: "numeric", month: "2-digit", day: "2-digit",
    hour: "2-digit", minute: "2-digit", second: "2-digit",
  }).formatToParts(lahza);
  const ol = (tur) => Number(q.find((p) => p.type === tur)?.value);
  const mahalliy = Date.UTC(ol("year"), ol("month") - 1, ol("day"),
    ol("hour"), ol("minute"), ol("second"));
  const butun = Math.floor(lahza.getTime() / 1000) * 1000;
  return Math.round((mahalliy - butun) / 60_000);
}
function kunBoshiUtc(sana, tz) {
  const [y, m, d] = sana.split("-").map(Number);
  const taxmin = Date.UTC(y, m - 1, d);
  const birinchi = taxmin - siljish(tz, new Date(taxmin)) * 60_000;
  const s = siljish(tz, new Date(birinchi));
  return new Date(taxmin - s * 60_000).toISOString();
}
function keyingiKun(sana) {
  const [y, m, d] = sana.split("-").map(Number);
  const t = Date.UTC(y, m - 1, d + 1);
  return new Date(t).toISOString().slice(0, 10);
}
function kunOraligi(dan, gacha, tz) {
  if (dan > gacha) throw new RangeError("dan gacha'dan keyin");
  return { dan: kunBoshiUtc(dan, tz),
    gacha: kunBoshiUtc(keyingiKun(gacha), tz) };
}

const tosh = "Asia/Tashkent";
console.log(kunOraligi("2026-10-24", "2026-10-24", tosh));
const ny = kunOraligi("2026-03-08", "2026-03-08", "America/New_York");
console.log(ny, (Date.parse(ny.gacha) - Date.parse(ny.dan)) / 3.6e6);
try {
  kunOraligi("2026-10-25", "2026-10-24", "UTC");
} catch (x) {
  console.log(x.name, x.message);
}

Konsolda:

text
{ dan: '2026-10-23T19:00:00.000Z', gacha: '2026-10-24T19:00:00.000Z' }
{ dan: '2026-03-08T05:00:00.000Z', gacha: '2026-03-09T04:00:00.000Z' } 23
RangeError dan gacha'dan keyin

Nyu-Yorkda 8-mart kuni 23 soat: ertalab soat 02:00 da 03:00 ga sakraydi. Oraliq 05:00Z (qish, −5) dan 04:00Z (yoz, −4) gacha — "+24 soat" qo'shganda bir soat xato bo'lardi. dan > gacha — YYYY-MM-DD satrlari leksik solishtirishda ham to'g'ri tartiblanadi.

3-mashq (qiyin): "har kuni 09:00 Toshkent" — keyingi ishga tushish

keyingiIsh(hozir, soat, daqiqa, tz) yozing: berilgan UTC lahzadan keyingi birinchi "tz mintaqasida soat:daqiqa" lahzasini UTC ISO'da qaytarsin. siljish va kunBoshiUtc dan foydalaning (DST'ni hisobga olsin).

Yechim
js
function siljish(tz, lahza) {
  const q = new Intl.DateTimeFormat("en-US", {
    timeZone: tz, hourCycle: "h23",
    year: "numeric", month: "2-digit", day: "2-digit",
    hour: "2-digit", minute: "2-digit", second: "2-digit",
  }).formatToParts(lahza);
  const ol = (tur) => Number(q.find((p) => p.type === tur)?.value);
  const mahalliy = Date.UTC(ol("year"), ol("month") - 1, ol("day"),
    ol("hour"), ol("minute"), ol("second"));
  const butun = Math.floor(lahza.getTime() / 1000) * 1000;
  return Math.round((mahalliy - butun) / 60_000);
}
function mahalliySana(tz, lahza) {
  const s = siljish(tz, lahza);
  const t = lahza.getTime() + s * 60_000;
  return new Date(t).toISOString().slice(0, 10);
}
function lahzaga(sana, soat, daqiqa, tz) {
  const [y, m, d] = sana.split("-").map(Number);
  const taxmin = Date.UTC(y, m - 1, d, soat, daqiqa);
  const birinchi = taxmin - siljish(tz, new Date(taxmin)) * 60_000;
  return taxmin - siljish(tz, new Date(birinchi)) * 60_000;
}
function keyingiIsh(hozir, soat, daqiqa, tz) {
  const bugun = mahalliySana(tz, hozir);
  let t = lahzaga(bugun, soat, daqiqa, tz);
  if (t <= hozir.getTime()) {
    const [y, m, d] = bugun.split("-").map(Number);
    const ertaga = new Date(Date.UTC(y, m - 1, d + 1))
      .toISOString().slice(0, 10);
    t = lahzaga(ertaga, soat, daqiqa, tz);
  }
  return new Date(t).toISOString();
}

const tz = "Asia/Tashkent";
console.log(keyingiIsh(new Date("2026-10-24T03:00:00Z"), 9, 0, tz));
console.log(keyingiIsh(new Date("2026-10-24T04:00:00Z"), 9, 0, tz));
console.log(keyingiIsh(new Date("2026-10-23T20:00:00Z"), 9, 0, tz));

Konsolda:

text
2026-10-24T04:00:00.000Z
2026-10-25T04:00:00.000Z
2026-10-24T04:00:00.000Z

Uchinchi qator qiziq: UTC'da hali 23-oktabr, lekin Toshkentda allaqachon 24-oktabr 01:00 — demak "bugungi" 09:00 hali oldinda. mahalliySana aynan shuni hal qiladi. Ikkinchi qatorda vaqt aynan 09:00 — <= tufayli keyingi kunga o'tamiz (bir lahzada ikki marta ishga tushmasin).

4-mashq: API qadami

src/umumiy/sana.ts ni yozing, sxema.ts ga dan/gacha (haqiqiySana), sorov.ts ga filtrni qo'shing. test/sana.test.ts — Malikaning holati (18:30Z va 19:30Z). «API qadami» dagi qolgan fayllarni qo'shing (marshrut.ts, ishga-tushir.ts — omborYarat, matn 100) va tipli config darsidagi vaqtinchalik kesimni (new XotiraOmbori(), tz siz) holat 40 ko'rinishiga qaytaring. npm run check — 54/54. Commit — «API qadami» dagi kanon xabari bilan.

Yechim

Kod — «API qadami» bo'limidagi to'liq fayllar va farqlar. Soxta soat: new XotiraOmbori(() => new Date(vaqtlar.shift() ?? …)) — ombor soat parametrini oladi (24-qismdagi taymer va vaqt testlari g'oyasi). SAQLASH=fayl ni qo'lda tekshiring: vazifa qo'shing, serverni to'xtating, qayta ishga tushiring — vazifa joyida bo'lishi kerak (kanonda 208 baytli fayl).

11. Real ishda

  • "Vaqt zonasi xatosi" — production'dagi eng ko'p uchraydigan va eng kech topiladigan xatolardan. Hisobotlar, to'lov muddatlari, "bugun tug'ilgan kun" xabarlari — hammasi shu darsdagi qoidalarga tayanadi.
  • Postgres'da timestamptz va AT TIME ZONE 'Asia/Tashkent' — 26-qismda shu g'oyaning SQL ko'rinishi.
  • Intervyu savollari: "Sanani bazada qanday saqlaysiz?", "new Date('2026-10-24') va new Date('2026-10-24T00:00') farqi?", "DST bo'lgan mintaqada 'har kuni 09:00' ni qanday rejalashtirasiz?"

Xulosa

  • Saqlash va uzatish — UTC lahza, ISO 8601 Z bilan; mintaqa — faqat kiritish va ko'rsatishda.
  • Kalendar kuni lahza emas: Toshkent kuni = [oldingi kun 19:00Z, shu kun 19:00Z).
  • getDate()/getHours() va Z siz satrlar — jarayon TZ siga bog'liq; serverda ishlatmang.
  • Date noto'g'ri sanani jim to'g'rilaydi — toISOString().startsWith bilan tekshiring.
  • Intl formatlash ICU/CLDR versiyasiga bog'liq — saqlamang, tahlil qilmang, aynan solishtirmang.
  • Temporal Node 24 da faqat --harmony-temporal bilan; kanon Intl bilan yechdi. Cron — mintaqani aniq bering.

Keyingi dars: Logging asoslari — console.log o'rniga darajali JSON loglar: nimani yozish va nimani hech qachon yozmaslik.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Backend'da sana va vaqt: UTC'da saqlash, Asia/Tashkent kuni va Temporal — IlmHamroh