diff --git a/changelogs/unreleased/gh-6751-olson-timezones.md b/changelogs/unreleased/gh-6751-olson-timezones.md
new file mode 100644
index 0000000000000000000000000000000000000000..f9c577d4e9755d78598751daff416188e4fe052e
--- /dev/null
+++ b/changelogs/unreleased/gh-6751-olson-timezones.md
@@ -0,0 +1,4 @@
+## feature/datetime
+
+ * `isdst` field in datetime object is now properly calculated according to
+   IANA tzdata (aka Olson DB) rules for given date/time moment (gh-6751);
diff --git a/extra/exports b/extra/exports
index 9dc404f01087a8fc1621bf1e861f57486dbc42fa..c88f0304b4495a2988ae870c5b09342f8dfd61a4 100644
--- a/extra/exports
+++ b/extra/exports
@@ -432,6 +432,7 @@ title_set_status
 title_update
 tnt_datetime_datetime_sub
 tnt_datetime_increment_by
+tnt_datetime_isdst
 tnt_datetime_now
 tnt_datetime_parse_full
 tnt_datetime_parse_tz
diff --git a/src/lib/core/datetime.c b/src/lib/core/datetime.c
index 62fd5da0fa1b7e00d41bc4614eb62f7c9d92358a..a9d9bd84aab0b6eaf6a057f501731ba64b782dad 100644
--- a/src/lib/core/datetime.c
+++ b/src/lib/core/datetime.c
@@ -47,6 +47,48 @@ local_secs(const struct datetime *date)
 	return (int64_t)date->epoch + date->tzoffset * 60;
 }
 
+/**
+ * Resolve tzindex encoded timezone from @sa date using Olson facilities.
+ * @param[in] date decode input datetime value.
+ * @param[out] gmtoff return resolved timezone offset (in seconds).
+ * @param[out] isdst return resolved daylight saving time status for the zone.
+ */
+static inline bool
+datetime_timezone_lookup(const struct datetime *date, long *gmtoff, int *isdst)
+{
+	if (date->tzindex == 0)
+		return false;
+
+	struct tnt_tm tm = {.tm_epoch = date->epoch};
+	if (!timezone_tzindex_lookup(date->tzindex, &tm))
+		return false;
+
+	*gmtoff = tm.tm_gmtoff;
+	*isdst = tm.tm_isdst;
+
+	return true;
+}
+
+bool
+datetime_isdst(const struct datetime *date)
+{
+	int isdst = 0;
+	long gmtoff = 0;
+
+	return datetime_timezone_lookup(date, &gmtoff, &isdst) && (isdst != 0);
+}
+
+long
+datetime_gmtoff(const struct datetime *date)
+{
+	int isdst = 0;
+	long gmtoff = date->tzoffset * 60;
+
+	datetime_timezone_lookup(date, &gmtoff, &isdst);
+
+	return gmtoff;
+}
+
 void
 datetime_to_tm(const struct datetime *date, struct tnt_tm *tm)
 {
@@ -721,7 +763,7 @@ datetime_increment_by(struct datetime *self, int direction,
 	int64_t secs = local_secs(self);
 	int64_t dt = local_dt(secs);
 	int nsec = self->nsec;
-	int offset = self->tzindex;
+	int offset = self->tzoffset;
 
 	bool is_ymd_updated = false;
 	int64_t years = ival->year;
@@ -809,6 +851,7 @@ datetime_increment_by(struct datetime *self, int direction,
 
 	self->epoch = utc_secs(secs, offset);
 	self->nsec = nsec;
+	self->tzoffset = datetime_gmtoff(self) / 60;
 	return 0;
 }
 
diff --git a/src/lib/core/datetime.h b/src/lib/core/datetime.h
index ca5dbee1b44b92d56671814fdb1269e719ca94d2..91f8bce8aa5576a5ae56c4642b44621a7d672b95 100644
--- a/src/lib/core/datetime.h
+++ b/src/lib/core/datetime.h
@@ -164,6 +164,18 @@ datetime_to_tm(const struct datetime *date, struct tnt_tm *tm);
 bool
 tm_to_datetime(struct tnt_tm *tm, struct datetime *date);
 
+/**
+ * Return whether given @sa datetime moment is DST.
+ */
+bool
+datetime_isdst(const struct datetime *date);
+
+/**
+ * Return gmtoff of a given @sa datetime moment.
+ */
+long
+datetime_gmtoff(const struct datetime *date);
+
 /**
  * Parse datetime text in ISO-8601 given format, and construct output
  * datetime value
diff --git a/src/lib/tzcode/gen-zone-abbrevs.pl b/src/lib/tzcode/gen-zone-abbrevs.pl
index 37dda6219ff704aed3e0597eceea48574d6e3367..61ae07b428d047e96bd6ad1723724f9c1e51ed4d 100755
--- a/src/lib/tzcode/gen-zone-abbrevs.pl
+++ b/src/lib/tzcode/gen-zone-abbrevs.pl
@@ -110,6 +110,9 @@ BEGIN {
         TZ_MILITARY  => 0x04,
         TZ_AMBIGUOUS => 0x08,
         TZ_NYI       => 0x10,
+        TZ_OLSON     => 0x20,
+        TZ_ALIAS     => 0x40,
+        TZ_DST       => 0x80,
     );
     require constant; constant->import(\%flags);
     %FlagName = reverse %flags;
@@ -156,6 +159,10 @@ sub read_abbrevs_file($) {
             $offset = $h * 60 + $m;
         }
 
+        if (/(Daylight|Summer)/) {
+            $flags |= TZ_DST;
+        }
+
         if ( exists $ZoneAbbrevs{$encoded} ) {
             $flags |= TZ_AMBIGUOUS;
             $offset = 0;
diff --git a/src/lib/tzcode/timezone.c b/src/lib/tzcode/timezone.c
index c4cd0a5a2dc8e90e580cce4f45076ad608a8e801..b53777723dc5d924733e9f4da00b949ab0bdb859 100644
--- a/src/lib/tzcode/timezone.c
+++ b/src/lib/tzcode/timezone.c
@@ -79,6 +79,12 @@ timezone_flags(const struct date_time_zone *zone)
 	return zone->flags;
 }
 
+bool
+timezone_isdst(const struct date_time_zone *zone)
+{
+	return !!(zone->flags & TZ_DST);
+}
+
 const char*
 timezone_name(int64_t index)
 {
@@ -124,10 +130,9 @@ timezone_raw_lookup(const char *str, size_t len,
 
 	if (found != NULL) {
 		/* lua assumes that single bit is set, not both */
-		assert((found->flags & (TZ_NYI | TZ_AMBIGUOUS)) !=
-		       (TZ_NYI | TZ_AMBIGUOUS));
-		if (found->flags & (TZ_NYI | TZ_AMBIGUOUS))
-			return -found->flags;
+		assert((found->flags & TZ_ERROR_MASK) != TZ_ERROR_MASK);
+		if (found->flags & TZ_ERROR_MASK)
+			return -(found->flags & TZ_ERROR_MASK);
 		*zone = found;
 		return len;
 	}
@@ -147,7 +152,7 @@ timezone_tm_lookup(const char *str, size_t len,
 	if ((found->flags & TZ_OLSON) == 0) {
 		tm->tm_gmtoff = found->offset * 60;
 		tm->tm_tzindex = found->id;
-		tm->tm_isdst = false;
+		tm->tm_isdst = !!(found->flags & TZ_DST);
 		return rc;
 	}
 	timezone_t tz = tzalloc(str); // FIXME - cache loaded
@@ -199,3 +204,25 @@ timezone_epoch_lookup(const char *str, size_t len, time_t base,
 		tzfree(tz);
 	return 0;
 }
+
+bool
+timezone_tzindex_lookup(int16_t tzindex, struct tnt_tm *tm)
+{
+	assert(tm != NULL);
+	if (tzindex == 0)
+		return false;
+
+	timezone_t tz = tzalloc(timezone_name(tzindex));
+	if (tz == NULL)
+		return false;
+	time_t epoch = (int64_t)tm->tm_epoch;
+	struct tnt_tm *result = tnt_localtime_rz(tz, &epoch, tm);
+	if (result == NULL)
+		goto exit_failure;
+	tzfree(tz);
+	return true;
+exit_failure:
+	if (tz != NULL)
+		tzfree(tz);
+	return false;
+}
diff --git a/src/lib/tzcode/timezone.h b/src/lib/tzcode/timezone.h
index b8eaf8e3d9557d1429847b11eba1f536449e0327..ea9b3c65654d29ec62c6b398ee7126593b208918 100644
--- a/src/lib/tzcode/timezone.h
+++ b/src/lib/tzcode/timezone.h
@@ -21,6 +21,7 @@ enum {
 	TZ_NYI = 0x10,
 	TZ_OLSON = 0x20,
 	TZ_ALIAS = 0x40,
+	TZ_DST = 0x80,
 
 	TZ_ERROR_MASK = TZ_AMBIGUOUS | TZ_NYI,
 };
@@ -67,6 +68,13 @@ timezone_tm_lookup(const char *str, size_t len,
 		   const struct date_time_zone **zone,
 		   struct tnt_tm *tm);
 
+/**
+ * Parse timezone string corresponding to @sa tzindex like
+ * @sa timezone_tm_lookup does.
+ */
+bool
+timezone_tzindex_lookup(int16_t tzindex, struct tnt_tm *tm);
+
 /** Return offset in minutes for given zone */
 int16_t
 timezone_offset(const struct date_time_zone *zone);
@@ -76,6 +84,9 @@ timezone_index(const struct date_time_zone *zone);
 /** Return attributes flags for given zone */
 uint16_t
 timezone_flags(const struct date_time_zone *zone);
+/** Return DST flag for given zone */
+bool
+timezone_isdst(const struct date_time_zone *zone);
 /** Translate tzindex to zone name */
 const char*
 timezone_name(int64_t index);
diff --git a/src/lib/tzcode/timezones.h b/src/lib/tzcode/timezones.h
index b412b585b6c378cb92316c9cd35f4db08be9de15..12d76d8302dff0a554bc1da1aca1321f8b547239 100644
--- a/src/lib/tzcode/timezones.h
+++ b/src/lib/tzcode/timezones.h
@@ -37,13 +37,13 @@ ZONE_ABBREV( 104,  780, "ST", 0)
 ZONE_ABBREV( 112,    0, "UT", TZ_UTC|TZ_RFC)
 ZONE_ABBREV( 120,    0, "WT", 0)
 ZONE_ABBREV( 128,    0, "ACT", TZ_AMBIGUOUS)
-ZONE_ABBREV( 129,    0, "ADT", TZ_AMBIGUOUS)
+ZONE_ABBREV( 129,    0, "ADT", TZ_AMBIGUOUS|TZ_DST)
 ZONE_ABBREV( 130,    0, "AET", TZ_AMBIGUOUS)
 ZONE_ABBREV( 131,  270, "AFT", 0)
 ZONE_ABBREV( 132,    0, "AMT", TZ_AMBIGUOUS)
 ZONE_ABBREV( 133, -720, "AoE", 0)
 ZONE_ABBREV( 134, -180, "ART", 0)
-ZONE_ABBREV( 135,    0, "AST", TZ_AMBIGUOUS)
+ZONE_ABBREV( 135,    0, "AST", TZ_AMBIGUOUS|TZ_DST)
 ZONE_ABBREV( 136,  240, "AZT", 0)
 ZONE_ABBREV( 144,    0, "BDT", TZ_AMBIGUOUS)
 ZONE_ABBREV( 145,  480, "BNT", 0)
@@ -53,7 +53,7 @@ ZONE_ABBREV( 148,    0, "BST", TZ_AMBIGUOUS)
 ZONE_ABBREV( 149,  360, "BTT", 0)
 ZONE_ABBREV( 152,  120, "CAT", 0)
 ZONE_ABBREV( 153,  390, "CCT", 0)
-ZONE_ABBREV( 154, -300, "CDT", TZ_RFC|TZ_AMBIGUOUS)
+ZONE_ABBREV( 154, -300, "CDT", TZ_RFC|TZ_AMBIGUOUS|TZ_DST)
 ZONE_ABBREV( 155,   60, "CET", 0)
 ZONE_ABBREV( 156, -300, "CIT", 0)
 ZONE_ABBREV( 157, -600, "CKT", 0)
@@ -64,7 +64,7 @@ ZONE_ABBREV( 161,  -60, "CVT", 0)
 ZONE_ABBREV( 162,  420, "CXT", 0)
 ZONE_ABBREV( 168,  180, "EAT", 0)
 ZONE_ABBREV( 169,    0, "ECT", TZ_AMBIGUOUS)
-ZONE_ABBREV( 170, -240, "EDT", TZ_RFC|TZ_AMBIGUOUS)
+ZONE_ABBREV( 170, -240, "EDT", TZ_RFC|TZ_AMBIGUOUS|TZ_DST)
 ZONE_ABBREV( 171,  120, "EET", 0)
 ZONE_ABBREV( 172,  -60, "EGT", 0)
 ZONE_ABBREV( 173, -300, "EST", TZ_RFC|TZ_AMBIGUOUS)
@@ -83,7 +83,7 @@ ZONE_ABBREV( 194, -240, "HAE", 0)
 ZONE_ABBREV( 195, -420, "HAP", 0)
 ZONE_ABBREV( 196, -360, "HAR", 0)
 ZONE_ABBREV( 197,  -90, "HAT", 0)
-ZONE_ABBREV( 198, -540, "HDT", 0)
+ZONE_ABBREV( 198, -540, "HDT", TZ_DST)
 ZONE_ABBREV( 199,  480, "HKT", 0)
 ZONE_ABBREV( 200, -210, "HLV", 0)
 ZONE_ABBREV( 201, -240, "HNA", 0)
@@ -94,7 +94,7 @@ ZONE_ABBREV( 205, -420, "HNR", 0)
 ZONE_ABBREV( 206, -150, "HNT", 0)
 ZONE_ABBREV( 207, -600, "HST", 0)
 ZONE_ABBREV( 208,  420, "ICT", 0)
-ZONE_ABBREV( 209,    0, "IDT", TZ_AMBIGUOUS)
+ZONE_ABBREV( 209,    0, "IDT", TZ_AMBIGUOUS|TZ_DST)
 ZONE_ABBREV( 210,  360, "IOT", 0)
 ZONE_ABBREV( 211,    0, "IST", TZ_AMBIGUOUS)
 ZONE_ABBREV( 216,  540, "JST", 0)
@@ -102,25 +102,25 @@ ZONE_ABBREV( 224,  360, "KGT", 0)
 ZONE_ABBREV( 225,  300, "KIT", 0)
 ZONE_ABBREV( 226,  540, "KST", 0)
 ZONE_ABBREV( 232,  180, "MCK", 0)
-ZONE_ABBREV( 233, -360, "MDT", TZ_RFC)
+ZONE_ABBREV( 233, -360, "MDT", TZ_RFC|TZ_DST)
 ZONE_ABBREV( 234,   60, "MEZ", 0)
 ZONE_ABBREV( 235,  720, "MHT", 0)
 ZONE_ABBREV( 236,  390, "MMT", 0)
-ZONE_ABBREV( 237,  240, "MSD", 0)
+ZONE_ABBREV( 237,  240, "MSD", TZ_DST)
 ZONE_ABBREV( 238,  180, "MSK", 0)
 ZONE_ABBREV( 239, -420, "MST", TZ_RFC|TZ_AMBIGUOUS)
 ZONE_ABBREV( 240,  240, "MUT", 0)
 ZONE_ABBREV( 241,  300, "MVT", 0)
 ZONE_ABBREV( 242,  480, "MYT", 0)
 ZONE_ABBREV( 248,  660, "NCT", 0)
-ZONE_ABBREV( 249,  -90, "NDT", 0)
+ZONE_ABBREV( 249,  -90, "NDT", TZ_DST)
 ZONE_ABBREV( 250,  660, "NFT", 0)
 ZONE_ABBREV( 251,  345, "NPT", 0)
 ZONE_ABBREV( 252,  720, "NRT", 0)
 ZONE_ABBREV( 253, -150, "NST", 0)
 ZONE_ABBREV( 254, -660, "NUT", 0)
 ZONE_ABBREV( 256,  120, "OEZ", 0)
-ZONE_ABBREV( 264, -420, "PDT", TZ_RFC)
+ZONE_ABBREV( 264, -420, "PDT", TZ_RFC|TZ_DST)
 ZONE_ABBREV( 265, -300, "PET", 0)
 ZONE_ABBREV( 266,  600, "PGT", 0)
 ZONE_ABBREV( 267,  480, "PHT", 0)
@@ -148,7 +148,7 @@ ZONE_ABBREV( 298,  300, "UZT", 0)
 ZONE_ABBREV( 304, -210, "VET", 0)
 ZONE_ABBREV( 305,  660, "VUT", 0)
 ZONE_ABBREV( 312,    0, "WAT", TZ_AMBIGUOUS)
-ZONE_ABBREV( 313,  540, "WDT", 0)
+ZONE_ABBREV( 313,  540, "WDT", TZ_DST)
 ZONE_ABBREV( 314,    0, "WET", 0)
 ZONE_ABBREV( 315,    0, "WEZ", 0)
 ZONE_ABBREV( 316,  720, "WFT", 0)
@@ -156,78 +156,78 @@ ZONE_ABBREV( 317, -180, "WGT", 0)
 ZONE_ABBREV( 318,  420, "WIB", 0)
 ZONE_ABBREV( 319,  540, "WIT", 0)
 ZONE_ABBREV( 320,    0, "WST", TZ_AMBIGUOUS)
-ZONE_ABBREV( 328,  630, "ACDT", 0)
+ZONE_ABBREV( 328,  630, "ACDT", TZ_DST)
 ZONE_ABBREV( 329,  570, "ACST", 0)
-ZONE_ABBREV( 330,    0, "ADST", TZ_AMBIGUOUS)
-ZONE_ABBREV( 331,  660, "AEDT", 0)
+ZONE_ABBREV( 330,    0, "ADST", TZ_AMBIGUOUS|TZ_DST)
+ZONE_ABBREV( 331,  660, "AEDT", TZ_DST)
 ZONE_ABBREV( 332,  600, "AEST", 0)
-ZONE_ABBREV( 333, -480, "AKDT", 0)
+ZONE_ABBREV( 333, -480, "AKDT", TZ_DST)
 ZONE_ABBREV( 334, -540, "AKST", 0)
 ZONE_ABBREV( 335,  360, "ALMT", 0)
-ZONE_ABBREV( 336,  300, "AMDT", 0)
-ZONE_ABBREV( 337,    0, "AMST", TZ_AMBIGUOUS)
+ZONE_ABBREV( 336,  300, "AMDT", TZ_DST)
+ZONE_ABBREV( 337,    0, "AMST", TZ_AMBIGUOUS|TZ_DST)
 ZONE_ABBREV( 338,  720, "ANAT", 0)
 ZONE_ABBREV( 339,  300, "AQTT", 0)
-ZONE_ABBREV( 340,  540, "AWDT", 0)
+ZONE_ABBREV( 340,  540, "AWDT", TZ_DST)
 ZONE_ABBREV( 341,  480, "AWST", 0)
 ZONE_ABBREV( 342,  -60, "AZOT", 0)
-ZONE_ABBREV( 343,  300, "AZST", 0)
-ZONE_ABBREV( 344,   60, "BDST", 0)
-ZONE_ABBREV( 345, -120, "BRST", 0)
+ZONE_ABBREV( 343,  300, "AZST", TZ_DST)
+ZONE_ABBREV( 344,   60, "BDST", TZ_DST)
+ZONE_ABBREV( 345, -120, "BRST", TZ_DST)
 ZONE_ABBREV( 352,  480, "CAST", 0)
-ZONE_ABBREV( 353,    0, "CDST", TZ_AMBIGUOUS)
-ZONE_ABBREV( 354,  120, "CEDT", 0)
-ZONE_ABBREV( 355,  120, "CEST", 0)
+ZONE_ABBREV( 353,    0, "CDST", TZ_AMBIGUOUS|TZ_DST)
+ZONE_ABBREV( 354,  120, "CEDT", TZ_DST)
+ZONE_ABBREV( 355,  120, "CEST", TZ_DST)
 ZONE_ABBREV( 356,  480, "CHOT", 0)
 ZONE_ABBREV( 357,  600, "ChST", 0)
 ZONE_ABBREV( 358,  600, "CHUT", 0)
 ZONE_ABBREV( 359, -300, "CIST", 0)
-ZONE_ABBREV( 360, -180, "CLDT", 0)
+ZONE_ABBREV( 360, -180, "CLDT", TZ_DST)
 ZONE_ABBREV( 361,    0, "CLST", TZ_AMBIGUOUS)
 ZONE_ABBREV( 368,  420, "DAVT", 0)
 ZONE_ABBREV( 369,  600, "DDUT", 0)
-ZONE_ABBREV( 376, -300, "EADT", 0)
+ZONE_ABBREV( 376, -300, "EADT", TZ_DST)
 ZONE_ABBREV( 377, -300, "EAST", 0)
-ZONE_ABBREV( 378,  120, "ECST", 0)
-ZONE_ABBREV( 379,    0, "EDST", TZ_AMBIGUOUS)
-ZONE_ABBREV( 380,  180, "EEDT", 0)
-ZONE_ABBREV( 381,  180, "EEST", 0)
-ZONE_ABBREV( 382,    0, "EGST", 0)
-ZONE_ABBREV( 384,  780, "FJDT", 0)
-ZONE_ABBREV( 385,  780, "FJST", 0)
-ZONE_ABBREV( 386, -180, "FKDT", 0)
-ZONE_ABBREV( 387,    0, "FKST", TZ_AMBIGUOUS)
+ZONE_ABBREV( 378,  120, "ECST", TZ_DST)
+ZONE_ABBREV( 379,    0, "EDST", TZ_AMBIGUOUS|TZ_DST)
+ZONE_ABBREV( 380,  180, "EEDT", TZ_DST)
+ZONE_ABBREV( 381,  180, "EEST", TZ_DST)
+ZONE_ABBREV( 382,    0, "EGST", TZ_DST)
+ZONE_ABBREV( 384,  780, "FJDT", TZ_DST)
+ZONE_ABBREV( 385,  780, "FJST", TZ_DST)
+ZONE_ABBREV( 386, -180, "FKDT", TZ_DST)
+ZONE_ABBREV( 387,    0, "FKST", TZ_AMBIGUOUS|TZ_DST)
 ZONE_ABBREV( 392, -360, "GALT", 0)
 ZONE_ABBREV( 393, -540, "GAMT", 0)
 ZONE_ABBREV( 394,  720, "GILT", 0)
-ZONE_ABBREV( 400, -540, "HADT", 0)
+ZONE_ABBREV( 400, -540, "HADT", TZ_DST)
 ZONE_ABBREV( 401, -600, "HAST", 0)
 ZONE_ABBREV( 402,  420, "HOVT", 0)
-ZONE_ABBREV( 408,  270, "IRDT", 0)
+ZONE_ABBREV( 408,  270, "IRDT", TZ_DST)
 ZONE_ABBREV( 409,  480, "IRKT", 0)
-ZONE_ABBREV( 410,    0, "IRST", TZ_AMBIGUOUS)
+ZONE_ABBREV( 410,    0, "IRST", TZ_AMBIGUOUS|TZ_DST)
 ZONE_ABBREV( 416,  660, "KOST", 0)
 ZONE_ABBREV( 417,  420, "KRAT", 0)
 ZONE_ABBREV( 418,  240, "KUYT", 0)
-ZONE_ABBREV( 424,  660, "LHDT", 0)
+ZONE_ABBREV( 424,  660, "LHDT", TZ_DST)
 ZONE_ABBREV( 425,  630, "LHST", 0)
 ZONE_ABBREV( 426,  840, "LINT", 0)
 ZONE_ABBREV( 432,  600, "MAGT", 0)
 ZONE_ABBREV( 433, -510, "MART", 0)
 ZONE_ABBREV( 434,  300, "MAWT", 0)
-ZONE_ABBREV( 435, -360, "MDST", 0)
+ZONE_ABBREV( 435, -360, "MDST", TZ_DST)
 ZONE_ABBREV( 436,  120, "MESZ", 0)
-ZONE_ABBREV( 440,  720, "NFDT", 0)
+ZONE_ABBREV( 440,  720, "NFDT", TZ_DST)
 ZONE_ABBREV( 441,  360, "NOVT", 0)
-ZONE_ABBREV( 442,  780, "NZDT", 0)
+ZONE_ABBREV( 442,  780, "NZDT", TZ_DST)
 ZONE_ABBREV( 443,  720, "NZST", 0)
 ZONE_ABBREV( 448,  180, "OESZ", 0)
 ZONE_ABBREV( 449,  360, "OMST", 0)
 ZONE_ABBREV( 450,  300, "ORAT", 0)
-ZONE_ABBREV( 456, -420, "PDST", 0)
+ZONE_ABBREV( 456, -420, "PDST", TZ_DST)
 ZONE_ABBREV( 457,  720, "PETT", 0)
 ZONE_ABBREV( 458,  780, "PHOT", 0)
-ZONE_ABBREV( 459, -120, "PMDT", 0)
+ZONE_ABBREV( 459, -120, "PMDT", TZ_DST)
 ZONE_ABBREV( 460, -180, "PMST", 0)
 ZONE_ABBREV( 461,  660, "PONT", 0)
 ZONE_ABBREV( 462,    0, "PYST", TZ_AMBIGUOUS)
@@ -239,56 +239,56 @@ ZONE_ABBREV( 482,  120, "SAST", 0)
 ZONE_ABBREV( 483,  660, "SRET", 0)
 ZONE_ABBREV( 484,  180, "SYOT", 0)
 ZONE_ABBREV( 488, -600, "TAHT", 0)
-ZONE_ABBREV( 489,  840, "TOST", 0)
+ZONE_ABBREV( 489,  840, "TOST", TZ_DST)
 ZONE_ABBREV( 496,  480, "ULAT", 0)
-ZONE_ABBREV( 497, -120, "UYST", 0)
+ZONE_ABBREV( 497, -120, "UYST", TZ_DST)
 ZONE_ABBREV( 504,  600, "VLAT", 0)
 ZONE_ABBREV( 505,  360, "VOST", 0)
 ZONE_ABBREV( 512,  720, "WAKT", 0)
-ZONE_ABBREV( 513,  120, "WAST", 0)
-ZONE_ABBREV( 514,   60, "WEDT", 0)
-ZONE_ABBREV( 515,   60, "WEST", 0)
+ZONE_ABBREV( 513,  120, "WAST", TZ_DST)
+ZONE_ABBREV( 514,   60, "WEDT", TZ_DST)
+ZONE_ABBREV( 515,   60, "WEST", TZ_DST)
 ZONE_ABBREV( 516,   60, "WESZ", 0)
-ZONE_ABBREV( 517, -120, "WGST", 0)
+ZONE_ABBREV( 517, -120, "WGST", TZ_DST)
 ZONE_ABBREV( 518,  480, "WITA", 0)
 ZONE_ABBREV( 520,  540, "YAKT", 0)
 ZONE_ABBREV( 521,  600, "YAPT", 0)
 ZONE_ABBREV( 522,  300, "YEKT", 0)
 ZONE_ABBREV( 528,  525, "ACWST", 0)
-ZONE_ABBREV( 529,  720, "ANAST", 0)
-ZONE_ABBREV( 530,    0, "AZODT", 0)
-ZONE_ABBREV( 531,    0, "AZOST", TZ_AMBIGUOUS)
-ZONE_ABBREV( 536,  825, "CHADT", 0)
+ZONE_ABBREV( 529,  720, "ANAST", TZ_DST)
+ZONE_ABBREV( 530,    0, "AZODT", TZ_DST)
+ZONE_ABBREV( 531,    0, "AZOST", TZ_AMBIGUOUS|TZ_DST)
+ZONE_ABBREV( 536,  825, "CHADT", TZ_DST)
 ZONE_ABBREV( 537,  765, "CHAST", 0)
-ZONE_ABBREV( 538,  540, "CHODT", 0)
-ZONE_ABBREV( 539,  540, "CHOST", 0)
-ZONE_ABBREV( 540, -240, "CIDST", 0)
-ZONE_ABBREV( 544, -300, "EASST", 0)
+ZONE_ABBREV( 538,  540, "CHODT", TZ_DST)
+ZONE_ABBREV( 539,  540, "CHOST", TZ_DST)
+ZONE_ABBREV( 540, -240, "CIDST", TZ_DST)
+ZONE_ABBREV( 544, -300, "EASST", TZ_DST)
 ZONE_ABBREV( 545,  660, "EFATE", 0)
-ZONE_ABBREV( 552,  480, "HOVDT", 0)
-ZONE_ABBREV( 553,  480, "HOVST", 0)
-ZONE_ABBREV( 560,  540, "IRKST", 0)
-ZONE_ABBREV( 568,  480, "KRAST", 0)
-ZONE_ABBREV( 576,  720, "MAGST", 0)
-ZONE_ABBREV( 584, -300, "NACDT", 0)
+ZONE_ABBREV( 552,  480, "HOVDT", TZ_DST)
+ZONE_ABBREV( 553,  480, "HOVST", TZ_DST)
+ZONE_ABBREV( 560,  540, "IRKST", TZ_DST)
+ZONE_ABBREV( 568,  480, "KRAST", TZ_DST)
+ZONE_ABBREV( 576,  720, "MAGST", TZ_DST)
+ZONE_ABBREV( 584, -300, "NACDT", TZ_DST)
 ZONE_ABBREV( 585, -360, "NACST", 0)
-ZONE_ABBREV( 586, -240, "NAEDT", 0)
+ZONE_ABBREV( 586, -240, "NAEDT", TZ_DST)
 ZONE_ABBREV( 587, -300, "NAEST", 0)
-ZONE_ABBREV( 588, -360, "NAMDT", 0)
+ZONE_ABBREV( 588, -360, "NAMDT", TZ_DST)
 ZONE_ABBREV( 589, -420, "NAMST", 0)
-ZONE_ABBREV( 590, -420, "NAPDT", 0)
+ZONE_ABBREV( 590, -420, "NAPDT", TZ_DST)
 ZONE_ABBREV( 591, -480, "NAPST", 0)
-ZONE_ABBREV( 592,  420, "NOVST", 0)
-ZONE_ABBREV( 600,  420, "OMSST", 0)
-ZONE_ABBREV( 608,  720, "PETST", 0)
-ZONE_ABBREV( 616,  240, "SAMST", 0)
-ZONE_ABBREV( 624,  540, "ULAST", 0)
-ZONE_ABBREV( 632,  660, "VLAST", 0)
-ZONE_ABBREV( 640, -180, "WARST", 0)
-ZONE_ABBREV( 648,  600, "YAKST", 0)
-ZONE_ABBREV( 649,  360, "YEKST", 0)
-ZONE_ABBREV( 656,  540, "CHODST", 0)
-ZONE_ABBREV( 664,  480, "HOVDST", 0)
+ZONE_ABBREV( 592,  420, "NOVST", TZ_DST)
+ZONE_ABBREV( 600,  420, "OMSST", TZ_DST)
+ZONE_ABBREV( 608,  720, "PETST", TZ_DST)
+ZONE_ABBREV( 616,  240, "SAMST", TZ_DST)
+ZONE_ABBREV( 624,  540, "ULAST", TZ_DST)
+ZONE_ABBREV( 632,  660, "VLAST", TZ_DST)
+ZONE_ABBREV( 640, -180, "WARST", TZ_DST)
+ZONE_ABBREV( 648,  600, "YAKST", TZ_DST)
+ZONE_ABBREV( 649,  360, "YEKST", TZ_DST)
+ZONE_ABBREV( 656,  540, "CHODST", TZ_DST)
+ZONE_ABBREV( 664,  480, "HOVDST", TZ_DST)
 ZONE_UNIQUE( 672, "Africa/Abidjan")
 ZONE_UNIQUE( 673, "Africa/Algiers")
 ZONE_UNIQUE( 674, "Africa/Bissau")
diff --git a/src/lua/datetime.lua b/src/lua/datetime.lua
index f5c53932de39c5107b90442526490c96be59f89b..7a44fb02d2da2a7bd10d427d7dc6360c5ad4bc6e 100644
--- a/src/lua/datetime.lua
+++ b/src/lua/datetime.lua
@@ -77,6 +77,7 @@ size_t tnt_datetime_strptime(struct datetime *date, const char *buf,
 void   tnt_datetime_now(struct datetime *now);
 bool   tnt_datetime_totable(const struct datetime *date,
                             struct interval *out);
+bool   tnt_datetime_isdst(const struct datetime *date);
 
 /* Tarantool interval support functions */
 size_t tnt_interval_to_string(const struct interval *, char *, ssize_t);
@@ -446,6 +447,11 @@ local function epoch_from_dt(dt)
     return (dt - DAYS_EPOCH_OFFSET) * SECS_PER_DAY
 end
 
+-- Use Olson facilities to determine whether local time in obj is DST
+local function datetime_isdst(obj)
+    return builtin.tnt_datetime_isdst(obj)
+end
+
 --[[
     Parse timezone name similar way as datetime_parse_full parse
     full literal.
@@ -953,7 +959,7 @@ local function datetime_totable(self)
         hour = tmp_ival.hour,
         min = tmp_ival.min,
         sec = tmp_ival.sec,
-        isdst = false,
+        isdst = datetime_isdst(self),
         nsec = self.nsec,
         tzoffset = self.tzoffset,
     }
@@ -1163,7 +1169,7 @@ local datetime_index_fields = {
     sec = function(self) return self.epoch % 60 end,
     usec = function(self) return self.nsec / 1e3 end,
     msec = function(self) return self.nsec / 1e6 end,
-    isdst = function(_) return false end,
+    isdst = function(self) return datetime_isdst(self) end,
     tz = function(self) return self:format('%Z') end,
 }
 
diff --git a/src/lua/tnt_datetime.c b/src/lua/tnt_datetime.c
index f243d8e3c5ee62764523e39e4f64741c78017085..e948057c8a6ab83e0c85ae247bb5ab58ca1e4fa2 100644
--- a/src/lua/tnt_datetime.c
+++ b/src/lua/tnt_datetime.c
@@ -96,3 +96,9 @@ tnt_interval_unpack(const char **data, struct interval *itv)
 {
 	return interval_unpack(data, itv);
 }
+
+bool
+tnt_datetime_isdst(const struct datetime *date)
+{
+	return datetime_isdst(date);
+}
diff --git a/test/app-tap/datetime.test.lua b/test/app-tap/datetime.test.lua
index 08988cf84c126adafc84f8683bf0e3bf7ef7540b..dca81104cd37feece8d7731e815b1ed985fecef3 100755
--- a/test/app-tap/datetime.test.lua
+++ b/test/app-tap/datetime.test.lua
@@ -11,7 +11,7 @@ local ffi = require('ffi')
 --]]
 if jit.arch == 'arm64' then jit.off() end
 
-test:plan(37)
+test:plan(38)
 
 -- minimum supported date - -5879610-06-22
 local MIN_DATE_YEAR = -5879610
@@ -676,6 +676,59 @@ test:test("Parsing of timezone names (errors)", function(test)
     end
 end)
 
+test:test("Daylight saving checks", function (test)
+    --[[
+        Check various dates in `Europe/Moscow` timezone for their
+        proper daylight saving settings.
+
+        Tzdata defines these rules for `Europe/Moscow` time-zone:
+```
+Zone Europe/Moscow  2:30:17 -       LMT 1880
+                    2:30:17 -       MMT 1916 Jul  3 # Moscow Mean Time
+                    2:31:19 Russia  %s  1919 Jul  1  0:00u
+                    3:00    Russia  %s  1921 10
+                    3:00    Russia  Europe/Moscow/Europe/Moscow 1922 10
+                    2:00    -       EET 1930 Jun 21
+                    3:00    Russia  Europe/Moscow/Europe/Moscow 1991 03 31 2:00s
+                    2:00    Russia  EE%sT 1992 Jan 19  2:00s
+                    3:00    Russia  Europe/Moscow/Europe/Moscow 2011 03 27 2:00s
+                    4:00    -       Europe/Moscow 2014 10 26 2:00s
+                    3:00    -       Europe/Moscow
+```
+        Either you could see the same table dumped in more or less
+        human-readable form using `zdump` utility:
+
+        `zdump -c 2004,2022 -v Europe/Moscow`
+    ]]
+    test:plan(30)
+    local moments = {
+        -- string, isdst?, tzoffset (mins)
+        {'2004-10-31T02:00:00 Europe/Moscow', false, 3 * 60},
+        {'2005-03-27T03:00:00 Europe/Moscow', true, 4 * 60},
+        {'2005-10-30T02:00:00 Europe/Moscow', false, 3 * 60},
+        {'2006-03-26T03:00:00 Europe/Moscow', true, 4 * 60},
+        {'2006-10-29T02:00:00 Europe/Moscow', false, 3 * 60},
+        {'2007-03-25T03:00:00 Europe/Moscow', true, 4 * 60},
+        {'2007-10-28T02:00:00 Europe/Moscow', false, 3 * 60},
+        {'2008-03-30T03:00:00 Europe/Moscow', true, 4 * 60},
+        {'2008-10-26T02:00:00 Europe/Moscow', false, 3 * 60},
+        {'2009-03-29T03:00:00 Europe/Moscow', true, 4 * 60},
+        {'2009-10-25T02:00:00 Europe/Moscow', false, 3 * 60},
+        {'2010-03-28T03:00:00 Europe/Moscow', true, 4 * 60},
+        {'2010-10-31T02:00:00 Europe/Moscow', false, 3 * 60},
+        {'2011-03-27T03:00:00 Europe/Moscow', false, 4 * 60},
+        {'2014-10-26T01:00:00 Europe/Moscow', false, 3 * 60},
+    }
+    for _, row in pairs(moments) do
+        local str, isdst, tzoffset = unpack(row)
+        local dt = date.parse(str)
+        test:is(dt.isdst, isdst,
+                ('%s: isdst = %s'):format(tostring(dt), dt.isdst))
+        test:is(dt.tzoffset, tzoffset,
+                ('%s: tzoffset = %s'):format(tostring(dt), dt.tzoffset))
+    end
+end)
+
 test:test("Datetime string formatting", function(test)
     test:plan(10)
     local t = date.new()