-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtimezone-worker.js
More file actions
151 lines (132 loc) · 4.65 KB
/
timezone-worker.js
File metadata and controls
151 lines (132 loc) · 4.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
const TIMEZONE_FILE_URL = "https://picoreplayer.github.io/timezone-data/timezones.db";
const KV_CACHE_KEY = "timezones_db_data";
const KV_CACHE_TTL = 86400; // 1 day in seconds
// Cache the Olson-to-Linux TZ mapping in memory to avoid frequent lookups
let olsonToLinuxTZ = {};
let tzDataVersion = "";
let tzDataBuild = "";
// Parse timezone data from text
function parseTimezoneData(text) {
const lines = text.split("\n");
const mapping = {};
let version = "";
let build = "";
for (const line of lines) {
if (line.trim() === "") {
continue; // Skip empty lines
}
// Parse the header comment to extract version and build date
// Format: "# This file is based on iana.org tzdata 2025c built on 2025-12-19 01:09:27 UTC"
if (line.startsWith("#") && line.includes("tzdata")) {
const match = line.match(/tzdata\s+(\S+)\s+built\s+on\s+(.+)$/);
if (match) {
version = match[1];
build = match[2];
}
continue;
}
if (line.startsWith("#")) {
continue; // Skip other comments
}
const [olsonTimezone, linuxTZ] = line.split(" ");
mapping[olsonTimezone] = linuxTZ;
}
return { mapping, version, build };
}
// Fetch and parse the timezone data with KV caching
async function loadTimezoneData(kvNamespace) {
try {
// First, try to get data from KV storage
if (kvNamespace) {
const cachedData = await kvNamespace.get(KV_CACHE_KEY);
if (cachedData) {
const parsedData = parseTimezoneData(cachedData);
tzDataVersion = parsedData.version;
tzDataBuild = parsedData.build;
return parsedData.mapping;
}
}
} catch (kvError) {
// Log KV error but continue to fetch from URL
console.error("Failed to retrieve timezone data from KV cache:", kvError.message);
}
// If not in KV, fetch from URL
const response = await fetch(TIMEZONE_FILE_URL);
if (!response.ok) {
throw new Error("Failed to fetch timezone data");
}
const text = await response.text();
const parsedData = parseTimezoneData(text);
tzDataVersion = parsedData.version;
tzDataBuild = parsedData.build;
// Cache the raw text in KV with expiration
try {
if (kvNamespace) {
await kvNamespace.put(KV_CACHE_KEY, text, { expirationTtl: KV_CACHE_TTL });
}
} catch (kvError) {
// Log KV error but don't fail the request
console.error("Failed to cache timezone data in KV storage:", kvError.message);
}
return parsedData.mapping;
}
// Helper function to validate Linux TZ strings
function isValidLinuxTZ(tz) {
if (!tz) return false;
return /^[A-Z]{1,5}[-+]?[0-9:.]*.*$/.test(tz); // Regex for Linux TZ strings
}
// Main Cloudflare Worker logic
export default {
async fetch(request, env) {
// Load the timezone data if not already cached
if (Object.keys(olsonToLinuxTZ).length === 0) {
try {
olsonToLinuxTZ = await loadTimezoneData(env.TZ_NAMESPACE);
} catch (e) {
return new Response(
JSON.stringify({
error: "Unable to fetch timezone data",
message: e.message,
}),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
}
// Extract IP and geolocation data
const ipHeader = "CF-Connecting-IP";
const cf = request.cf || {}; // Handle the case where the `cf` object is undefined
const ip = request.headers.has(ipHeader) ? request.headers.get(ipHeader) : "not available";
const timezone = cf.timezone || "Not Provided";
// Resolve the Linux TZ string
let linuxTZ = "UTC"; // Default to UTC
if (timezone && olsonToLinuxTZ[timezone]) {
linuxTZ = isValidLinuxTZ(olsonToLinuxTZ[timezone]) ? olsonToLinuxTZ[timezone] : "UTC";
}
// Get the current epoch time
const currentEpochTime = Math.floor(Date.now() / 1000);
// Build the response data
const data = {
ip: ip,
Colo: cf.colo, // Cloudflare data center location
Country: cf.country,
City: cf.city,
Continent: cf.continent,
Latitude: cf.latitude,
Longitude: cf.longitude,
PostalCode: cf.postalCode,
MetroCode: cf.metroCode,
Region: cf.region,
RegionCode: cf.regionCode,
Timezone: timezone, // Olson timezone from the `cf` data
LinuxTZ: linuxTZ, // Resolved Linux Timezone string
currentEpochTime: currentEpochTime, // Current epoch time
TZdata_Version: tzDataVersion || null, // IANA tzdata version
TZdata_Build: tzDataBuild || null, // Build date and time
};
// Return the response as JSON
// return new Response(JSON.stringify(data, null, 2), {
// headers: { "Content-Type": "application/json" },
// });
return Response.json(data);
},
};