Côn Đảo không chỉ nổi tiếng với biển đảo và thiên nhiên hoang sơ mà còn là vùng đất lưu giữ nhiều dấu ấn lịch sử, văn hóa đặc biệt.
Hằng năm, Côn Đảo có nhiều ngày kỷ niệm, tưởng niệm các nhân vật lịch sử, sự kiện cách mạng cùng những dịp lễ truyền thống mang ý nghĩa sâu sắc.
Du khách có thể tham khảo Lịch sự kiện Côn Đảo để tìm hiểu các dấu mốc lịch sử, ngày lễ và hoạt động văn hóa, kết hợp lên kế hoạch tham quan phù hợp.
Tàu Cao Tốc Việt Nam đồng hành cùng du khách trên hành trình khám phá Côn Đảo.
(function () {
“use strict”;
function init() {
const root = document.getElementById(“cde-widget”);
if (!root || root.dataset.ready) return;
root.dataset.ready = “1”;
const API =
“https://condaoexpress.com/wp-json/wp/v2/” +
“pages/217?_fields=id,content”;
const month = root.querySelector(“#cde-month”);
const year = root.querySelector(“#cde-year”);
const status = root.querySelector(“#cde-status”);
const list = root.querySelector(“#cde-list”);
const upcoming = root.querySelector(“#cde-upcoming”);
const current = root.querySelector(“#cde-current”);
const today = new Date();
let events = [];
let mode = “month”;
let loaded = false;
function make(tag, value) {
const node = document.createElement(tag);
if (value !== undefined) node.textContent = value;
return node;
}
function ensureYear(y) {
if (![…year.options].some(o => +o.value === y)) {
const option = make(“option”, String(y));
option.value = String(y);
year.append(option);
}
}
ensureYear(today.getFullYear());
month.value = String(today.getMonth() + 1);
year.value = String(today.getFullYear());
function dateOf(y, m, d) {
const date = new Date(y, m – 1, d);
if (
date.getFullYear() !== y ||
date.getMonth() !== m – 1 ||
date.getDate() !== d
) return null;
return date;
}
function parseDate(s) {
if (typeof s !== “string” ||
!/^d{4}-d{2}-d{2}$/.test(s)) return null;
const [y,m,d] = s.split(“-“).map(Number);
return dateOf(y,m,d);
}
function fmt(d) {
return new Intl.DateTimeFormat(“vi-VN”, {
day:”2-digit”,month:”2-digit”,year:”numeric”
}).format(d);
}
function occurrence(e,y) {
if (e.schedule?.startDate) {
const start = parseDate(e.schedule.startDate);
const end = parseDate(
e.schedule.endDate || e.schedule.startDate
);
return start ? [{
e,date:start,end:end || start,
note:”Chương trình theo lịch công bố”
}] : [];
}
const r = e.recurrence;
if (!r || r.frequency !== “annual”) return [];
if (r.calendar === “solar”) {
const d = dateOf(y,+r.month,+r.day);
const original = e.originalDate
? parseDate(e.originalDate) : null;
return d && (!original || d >= original)
? [{
e,date:d,end:d,
note:`Ngày ${r.day}/${r.month} dương lịch`
}]
: [];
}
// Không suy diễn ngày dương lịch từ âm lịch.
if (r.calendar === “lunar”) {
return [{
e,date:null,end:null,
lunarMonth:+r.month,
note:`Ngày ${r.day}/${r.month} âm lịch`
}];
}
return [];
}
function render() {
if (!loaded) return;
list.replaceChildren();
const m = +month.value;
const y = +year.value;
const now = new Date();
const rows = [];
for (const e of events) {
const years = mode === “upcoming”
? [now.getFullYear(),now.getFullYear()+1]
: [y];
for (const yy of years) {
for (const item of occurrence(e,yy)) {
if (mode === “month”) {
const first = new Date(y,m-1,1);
const last = new Date(y,m,0,23,59,59);
if (
item.date &&
item.date <= last &&
item.end >= first
) rows.push(item);
else if (
!item.date &&
item.lunarMonth === m
) rows.push(item);
} else if (
item.date &&
item.end >= now
) rows.push(item);
}
}
}
rows.sort((a,b) =>
(a.date?.getTime() ?? Infinity) –
(b.date?.getTime() ?? Infinity)
);
const shown = mode === “upcoming”
? rows.slice(0,6) : rows;
shown.forEach(item => {
const e = item.e;
const box = make(“article”);
box.className = “cde-item”;
const date = make(
“div”,
item.date
? fmt(item.date) +
(item.end > item.date
? ” – ” + fmt(item.end) : “”)
: item.note
);
date.className = “cde-date”;
box.append(date);
box.append(make(“h3”,e.title?.vi || “Sự kiện”));
if (item.date) box.append(make(“p”,item.note));
if (e.location?.vi) {
box.append(make(“p”,e.location.vi));
}
if (e.articleUrl) {
try {
const url = new URL(e.articleUrl);
if (
url.protocol === “https:” &&
url.hostname === “condaoexpress.com”
) {
const a = make(“a”,”Xem chi tiết”);
a.href = url.href;
a.target = “_blank”;
a.rel = “noopener noreferrer”;
box.append(a);
}
} catch (_) {}
}
list.append(box);
});
status.textContent = shown.length
? “”
: “Chưa có sự kiện phù hợp trong dữ liệu hiện có.”;
upcoming.classList.toggle(
“active”,mode === “upcoming”
);
}
month.addEventListener(“change”,() => {
mode = “month”;
render();
});
year.addEventListener(“change”,() => {
mode = “month”;
render();
});
upcoming.addEventListener(“click”,() => {
mode = “upcoming”;
render();
});
current.addEventListener(“click”,() => {
const d = new Date();
ensureYear(d.getFullYear());
month.value = String(d.getMonth()+1);
year.value = String(d.getFullYear());
mode = “month”;
render();
});
async function load() {
const controller = new AbortController();
const timer = setTimeout(
() => controller.abort(),12000
);
try {
const response = await fetch(API,{
credentials:”omit”,
cache:”no-store”,
signal:controller.signal
});
if (!response.ok) {
throw new Error(“HTTP “+response.status);
}
const page = await response.json();
if (
page.id !== 217 ||
typeof page.content?.rendered !== “string”
) throw new Error(“Nguồn không hợp lệ”);
const doc = new DOMParser().parseFromString(
page.content.rendered,”text/html”
);
const source = doc.querySelector(
“#cde-events-data-v1”
);
if (!source) {
throw new Error(“Thiếu dữ liệu sự kiện”);
}
const data = JSON.parse(source.textContent);
if (!Array.isArray(data.events)) {
throw new Error(“Sai định dạng sự kiện”);
}
events = data.events.filter(e =>
e && typeof e.id === “string” &&
typeof e.title?.vi === “string” &&
[
“annual_observance”,
“historical_anniversary”,
“confirmed_program”
].includes(e.publicationStatus)
);
loaded = true;
render();
} catch (err) {
console.error(“CDE Calendar:”,err);
status.replaceChildren(
make(“span”,”Chưa tải được lịch. “)
);
const link = make(
“a”,”Xem trên Côn Đảo Express”
);
link.href =
“https://condaoexpress.com/” +
“lich-su-kien-con-dao-theo-thang/”;
link.target = “_blank”;
link.rel = “noopener”;
status.append(link);
} finally {
clearTimeout(timer);
}
}
load();
}
if (document.readyState === “loading”) {
document.addEventListener(“DOMContentLoaded”,init);
} else {
init();
}
})();

