#
tokens: 13839/50000 10/10 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── jest.config.js
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│   ├── index.ts
│   ├── server.ts
│   ├── sse.ts
│   └── timezone-utils.ts
├── tests
│   └── timezone-utils.test.ts
└── tsconfig.json
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Dependency directories
 2 | node_modules/
 3 | 
 4 | # Build output
 5 | dist/
 6 | 
 7 | # Logs
 8 | logs
 9 | *.log
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | 
14 | # Environment variables
15 | .env
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 | 
21 | # Editor directories and files
22 | .idea/
23 | .vscode/
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 | 
30 | # OS specific
31 | .DS_Store
32 | Thumbs.db 
33 | 
34 | # Test coverage
35 | coverage/
36 | .nyc_output/ 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # MCP DateTime
  2 | 
  3 | A TypeScript implementation of a Model Context Protocol (MCP) server that provides datetime and timezone information to agentic systems and chat REPLs.
  4 | 
  5 | ## Overview
  6 | 
  7 | MCP DateTime is a simple server that implements the [Model Context Protocol](https://github.com/model-context-protocol/mcp) to provide datetime and timezone information to AI agents and chat interfaces. It allows AI systems to:
  8 | 
  9 | - Get the current time in the local system timezone
 10 | - Get the current time in any valid timezone
 11 | - List all available timezones
 12 | - Access timezone information through URI resources
 13 | 
 14 | ## Installation
 15 | 
 16 | ### From npm
 17 | 
 18 | ```bash
 19 | npm install -g mcp-datetime
 20 | ```
 21 | 
 22 | ### From source
 23 | 
 24 | ```bash
 25 | git clone https://github.com/odgrmi/mcp-datetime.git
 26 | cd mcp-datetime
 27 | npm install
 28 | npm run build
 29 | ```
 30 | 
 31 | ## Usage
 32 | 
 33 | ### Command Line
 34 | 
 35 | MCP DateTime can be run in two modes:
 36 | 
 37 | #### 1. Standard I/O Mode (Default)
 38 | 
 39 | This mode is ideal for integrating with AI systems that support the MCP protocol through standard input/output:
 40 | 
 41 | ```bash
 42 | mcp-datetime
 43 | ```
 44 | 
 45 | #### 2. Server-Sent Events (SSE) Mode
 46 | 
 47 | This mode starts an HTTP server that provides SSE transport for the MCP protocol:
 48 | 
 49 | ```bash
 50 | mcp-datetime --sse
 51 | ```
 52 | 
 53 | You can also specify a custom port and URI prefix:
 54 | 
 55 | ```bash
 56 | mcp-datetime --sse --port=8080 --prefix=/api/datetime
 57 | ```
 58 | 
 59 | ### Environment Variables
 60 | 
 61 | - `PORT`: Sets the port for SSE mode (default: 3000)
 62 | - `URI_PREFIX`: Sets the URI prefix for SSE mode (default: none)
 63 | 
 64 | ## Available Tools
 65 | 
 66 | MCP DateTime provides the following tools:
 67 | 
 68 | ### `get-current-time`
 69 | 
 70 | Returns the current time in the system's local timezone.
 71 | 
 72 | ### `get-current-timezone`
 73 | 
 74 | Returns the current system timezone.
 75 | 
 76 | ### `get-time-in-timezone`
 77 | 
 78 | Returns the current time in a specified timezone.
 79 | 
 80 | Parameters:
 81 | - `timezone`: The timezone to get the current time for (e.g., "America/New_York")
 82 | 
 83 | ### `list-timezones`
 84 | 
 85 | Returns a list of all available timezones.
 86 | 
 87 | ## Resource URIs
 88 | 
 89 | MCP DateTime also provides access to timezone information through resource URIs:
 90 | 
 91 | ### `datetime://{timezone}`
 92 | 
 93 | Returns the current time in the specified timezone.
 94 | 
 95 | Example: `datetime://America/New_York`
 96 | 
 97 | ### `datetime://list`
 98 | 
 99 | Returns a list of all available timezones.
100 | 
101 | ## Common Timezones
102 | 
103 | The following common timezones are always available:
104 | 
105 | - UTC
106 | - Europe/London
107 | - Europe/Paris
108 | - Europe/Berlin
109 | - America/New_York
110 | - America/Chicago
111 | - America/Denver
112 | - America/Los_Angeles
113 | - Asia/Tokyo
114 | - Asia/Shanghai
115 | - Asia/Kolkata
116 | - Australia/Sydney
117 | - Pacific/Auckland
118 | 
119 | ## SSE Endpoints
120 | 
121 | When running in SSE mode, the following endpoints are available:
122 | 
123 | - `/sse`: SSE connection endpoint
124 | - `/message`: Message endpoint for client-to-server communication
125 | - `/info`: Basic server information
126 | 
127 | If a URI prefix is specified, it will be prepended to all endpoints.
128 | 
129 | ## Integration with AI Systems
130 | 
131 | MCP DateTime can be integrated with AI systems that support the Model Context Protocol. This allows AI agents to access accurate timezone and datetime information.
132 | 
133 | ## Development
134 | 
135 | ### Prerequisites
136 | 
137 | - Node.js 14.16 or higher
138 | - npm
139 | 
140 | ### Setup
141 | 
142 | ```bash
143 | git clone https://github.com/odgrim/mcp-datetime.git
144 | cd mcp-datetime
145 | npm install
146 | ```
147 | 
148 | ### Build
149 | 
150 | ```bash
151 | npm run build
152 | ```
153 | 
154 | ### Run in Development Mode
155 | 
156 | ```bash
157 | npm run dev        # Standard I/O mode
158 | npm run dev:sse    # SSE mode
159 | ```
160 | 
161 | ## License
162 | 
163 | This project is licensed under the Mozilla Public License 2.0 - see the [LICENSE](LICENSE) file for details. 
164 | 
```

--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------

```javascript
 1 | export default {
 2 |   preset: 'ts-jest',
 3 |   testEnvironment: 'node',
 4 |   extensionsToTreatAsEsm: ['.ts'],
 5 |   moduleNameMapper: {
 6 |     '^(\\.{1,2}/.*)\\.js$': '$1',
 7 |   },
 8 |   transform: {
 9 |     '^.+\\.tsx?$': [
10 |       'ts-jest',
11 |       {
12 |         useESM: true,
13 |       },
14 |     ],
15 |   },
16 |   collectCoverageFrom: [
17 |     'src/**/*.ts',
18 |     '!src/**/*.d.ts',
19 |     '!src/**/index.ts',
20 |   ],
21 |   coverageReporters: ['text', 'lcov', 'clover', 'html'],
22 |   coverageDirectory: 'coverage',
23 | }; 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "@odgrim/mcp-datetime",
 3 |   "version": "0.2.0",
 4 |   "description": "A TypeScript implementation of a simple MCP server that exposes datetime information to agentic systems and chat REPLs",
 5 |   "type": "module",
 6 |   "main": "dist/index.js",
 7 |   "bin": {
 8 |     "mcp-datetime": "dist/index.js"
 9 |   },
10 |   "files": [
11 |     "dist"
12 |   ],
13 |   "scripts": {
14 |     "build": "tsc && shx chmod +x dist/*.js",
15 |     "prepare": "npm run build",
16 |     "watch": "tsc --watch",
17 |     "start": "node dist/index.js",
18 |     "start:sse": "node dist/index.js --sse",
19 |     "dev": "ts-node --esm src/index.ts",
20 |     "dev:sse": "ts-node --esm src/index.ts --sse",
21 |     "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
22 |     "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
23 |   },
24 |   "keywords": [
25 |     "mcp",
26 |     "datetime",
27 |     "timezone",
28 |     "model-context-protocol"
29 |   ],
30 |   "author": "odgrim",
31 |   "license": "MPL-2.0",
32 |   "dependencies": {
33 |     "@modelcontextprotocol/sdk": "^1.4.1",
34 |     "@types/express": "^5.0.0",
35 |     "express": "^4.21.2",
36 |     "zod": "^3.22.4"
37 |   },
38 |   "devDependencies": {
39 |     "@types/jest": "^29.5.14",
40 |     "@types/node": "^20.11.24",
41 |     "jest": "^29.7.0",
42 |     "shx": "^0.3.4",
43 |     "ts-jest": "^29.2.6",
44 |     "ts-node": "^10.9.2",
45 |     "typescript": "^5.3.3"
46 |   },
47 |   "engines": {
48 |     "node": ">=14.16"
49 |   },
50 |   "publishConfig": {
51 |     "access": "public"
52 |   },
53 |   "repository": {
54 |     "type": "git",
55 |     "url": "git+https://github.com/odgrim/mcp-datetime.git"
56 |   },
57 |   "bugs": {
58 |     "url": "https://github.com/odgrim/mcp-datetime/issues"
59 |   },
60 |   "homepage": "https://github.com/odgrim/mcp-datetime#readme"
61 | }
62 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | import { server } from "./server.js";
 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 5 | import { startSSEServer } from "./sse.js";
 6 | 
 7 | async function main() {
 8 |   console.error("Starting MCP DateTime server...");
 9 |   
10 |   // Check if the --sse flag is provided
11 |   const useSSE = process.argv.includes("--sse");
12 |   
13 |   // Check if a custom port is provided with --port=XXXX
14 |   const portArg = process.argv.find(arg => arg.startsWith("--port="));
15 |   // Use PORT environment variable or command line argument or default to 3000
16 |   const defaultPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
17 |   const port = portArg ? parseInt(portArg.split("=")[1], 10) : defaultPort;
18 |   
19 |   // Check if a URI prefix is provided with --prefix=XXXX
20 |   const prefixArg = process.argv.find(arg => arg.startsWith("--prefix="));
21 |   // Use URI_PREFIX environment variable or command line argument or default to empty string
22 |   const defaultPrefix = process.env.URI_PREFIX || "";
23 |   const uriPrefix = prefixArg ? prefixArg.split("=")[1] : defaultPrefix;
24 | 
25 |   try {
26 |     if (useSSE) {
27 |       // Start the SSE server
28 |       console.error(`Starting MCP DateTime server with SSE transport on port ${port}...`);
29 |       const cleanup = startSSEServer(port, uriPrefix);
30 |       
31 |       // Handle graceful shutdown
32 |       process.on("SIGINT", async () => {
33 |         console.error("Received SIGINT, shutting down...");
34 |         await cleanup();
35 |         process.exit(0);
36 |       });
37 |       
38 |       process.on("SIGTERM", async () => {
39 |         console.error("Received SIGTERM, shutting down...");
40 |         await cleanup();
41 |         process.exit(0);
42 |       });
43 |     } else {
44 |       // Connect to stdio transport
45 |       await server.connect(new StdioServerTransport());
46 |       console.error("MCP DateTime server connected to stdio transport");
47 |     }
48 |   } catch (error) {
49 |     console.error("Error starting MCP DateTime server:", error);
50 |     process.exit(1);
51 |   }
52 | }
53 | 
54 | main().catch(error => {
55 |   console.error("Unhandled error:", error);
56 |   process.exit(1);
57 | }); 
```

--------------------------------------------------------------------------------
/src/sse.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
 2 | import express from "express";
 3 | import { server } from "./server.js";
 4 | 
 5 | /**
 6 |  * Creates and starts an Express server that provides SSE transport for the MCP DateTime server
 7 |  * @param port The port to listen on (defaults to PORT env var or 3000)
 8 |  * @param uriPrefix The URI prefix to prepend to all routes (for reverse proxy scenarios)
 9 |  * @returns A cleanup function to close the server
10 |  */
11 | export function startSSEServer(
12 |   port: number = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
13 |   uriPrefix: string = ""
14 | ): () => Promise<void> {
15 |   const app = express();
16 |   let transport: SSEServerTransport;
17 | 
18 |   // Normalize the URI prefix to ensure it starts with a / and doesn't end with one
19 |   const normalizedPrefix = uriPrefix 
20 |     ? (uriPrefix.startsWith('/') ? uriPrefix : `/${uriPrefix}`).replace(/\/+$/, '')
21 |     : '';
22 |   
23 |   // Define the endpoint paths with the prefix
24 |   const ssePath = `${normalizedPrefix}/sse`;
25 |   const messagePath = `${normalizedPrefix}/message`;
26 |   const infoPath = `${normalizedPrefix}/info`;
27 | 
28 |   // SSE endpoint for establishing a connection
29 |   app.get(ssePath, async (req, res) => {
30 |     console.error("Received SSE connection");
31 |     transport = new SSEServerTransport(messagePath, res);
32 |     await server.connect(transport);
33 |   });
34 | 
35 |   // Endpoint for receiving messages from the client
36 |   app.post(messagePath, async (req, res) => {
37 |     console.error("Received message");
38 |     await transport.handlePostMessage(req, res);
39 |   });
40 | 
41 |   // Basic info endpoint
42 |   app.get(infoPath, (req, res) => {
43 |     res.json({
44 |       name: "MCP DateTime Server",
45 |       version: "0.1.0",
46 |       transport: "SSE",
47 |       endpoints: {
48 |         sse: ssePath,
49 |         message: messagePath,
50 |         info: infoPath
51 |       }
52 |     });
53 |   });
54 | 
55 |   // Start the server
56 |   const httpServer = app.listen(port, () => {
57 |     console.error(`MCP DateTime server listening on port ${port}`);
58 |     console.error(`URI prefix: ${normalizedPrefix || '/'} (root)`);
59 |     console.error(`SSE endpoint: http://localhost:${port}${ssePath}`);
60 |     console.error(`Message endpoint: http://localhost:${port}${messagePath}`);
61 |     console.error(`Info endpoint: http://localhost:${port}${infoPath}`);
62 |   });
63 | 
64 |   // Return a cleanup function
65 |   return async () => {
66 |     console.error("Closing SSE server...");
67 |     httpServer.close();
68 |     if (transport) {
69 |       await server.close();
70 |     }
71 |   };
72 | } 
```

--------------------------------------------------------------------------------
/src/server.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { z } from "zod";
  3 | import { 
  4 |   COMMON_TIMEZONES, 
  5 |   getAvailableTimezones, 
  6 |   isValidTimezone, 
  7 |   getCurrentTimeInTimezone, 
  8 |   getCurrentTimezone,
  9 |   getFormattedTimezoneList
 10 | } from "./timezone-utils.js";
 11 | 
 12 | // Create an MCP server
 13 | export const server = new McpServer({
 14 |   name: "mcp-datetime",
 15 |   version: "0.1.0"
 16 | });
 17 | 
 18 | // Add a tool to get the current time in the local timezone
 19 | server.tool(
 20 |   "get-current-time",
 21 |   "Get the current time in the configured local timezone",
 22 |   async () => ({
 23 |     content: [{ 
 24 |       type: "text", 
 25 |       text: `The current time is ${getCurrentTimeInTimezone(getCurrentTimezone())}`
 26 |     }]
 27 |   })
 28 | );
 29 | 
 30 | // Add a tool to get the current system timezone
 31 | server.tool(
 32 |   "get-current-timezone",
 33 |   "Get the current system timezone",
 34 |   async () => {
 35 |     const timezone = getCurrentTimezone();
 36 |     return {
 37 |       content: [{ 
 38 |         type: "text", 
 39 |         text: `The current system timezone is ${timezone}`
 40 |       }]
 41 |     };
 42 |   }
 43 | );
 44 | 
 45 | // Add a tool to get the current time in a specific timezone
 46 | server.tool(
 47 |   "get-time-in-timezone",
 48 |   "Get the current time in a specific timezone",
 49 |   {
 50 |     timezone: z.string().describe("The timezone to get the current time for")
 51 |   },
 52 |   async (args) => {
 53 |     if (!isValidTimezone(args.timezone)) {
 54 |       return {
 55 |         content: [{ 
 56 |           type: "text", 
 57 |           text: `Error: Invalid timezone "${args.timezone}". Use the "list-timezones" tool to see available options.`
 58 |         }],
 59 |         isError: true
 60 |       };
 61 |     }
 62 |     
 63 |     return {
 64 |       content: [{ 
 65 |         type: "text", 
 66 |         text: `The current time in ${args.timezone} is ${getCurrentTimeInTimezone(args.timezone)}`
 67 |       }]
 68 |     };
 69 |   }
 70 | );
 71 | 
 72 | // Add a tool to list all available timezones
 73 | server.tool(
 74 |   "list-timezones",
 75 |   "List all available timezones",
 76 |   async () => {
 77 |     return {
 78 |       content: [{ 
 79 |         type: "text", 
 80 |         text: getFormattedTimezoneList()
 81 |       }]
 82 |     };
 83 |   }
 84 | );
 85 | 
 86 | // Create a resource template for datetime URIs
 87 | // The list method is defined to return a list of common timezones
 88 | // to avoid overwhelming the client with all available timezones
 89 | const datetimeTemplate = new ResourceTemplate("datetime://{timezone}", {
 90 |   list: async () => {
 91 |     return {
 92 |       resources: COMMON_TIMEZONES.map(timezone => ({
 93 |         uri: `datetime://${encodeURIComponent(timezone)}`,
 94 |         name: `Current time in ${timezone}`,
 95 |         description: `Get the current time in the ${timezone} timezone`,
 96 |         mimeType: "text/plain"
 97 |       }))
 98 |     };
 99 |   }
100 | });
101 | 
102 | // Register the template with the server
103 | server.resource(
104 |   "datetime-template",
105 |   datetimeTemplate,
106 |   async (uri, variables) => {
107 |     // Decode the timezone from the URI
108 |     const encodedTimezone = variables.timezone as string;
109 |     const timezone = decodeURIComponent(encodedTimezone);
110 |     
111 |     if (!timezone || !isValidTimezone(timezone)) {
112 |       throw new Error(`Invalid timezone: ${timezone}`);
113 |     }
114 |     
115 |     const formattedTime = getCurrentTimeInTimezone(timezone);
116 |     return {
117 |       contents: [{
118 |         uri: decodeURIComponent(uri.href),
119 |         text: `Current time in ${timezone}: ${formattedTime}`,
120 |         mimeType: "text/plain"
121 |       }]
122 |     };
123 |   }
124 | );
125 | 
126 | // Add a resource to list all available timezones
127 | server.resource(
128 |   "datetime-list",
129 |   "datetime://list",
130 |   async () => {
131 |     return {
132 |       contents: [{
133 |         uri: "datetime://list",
134 |         text: getFormattedTimezoneList("All available timezones"),
135 |         mimeType: "text/plain"
136 |       }]
137 |     };
138 |   }
139 | ); 
```

--------------------------------------------------------------------------------
/src/timezone-utils.ts:
--------------------------------------------------------------------------------

```typescript
  1 | // Common timezones to ensure they're always available
  2 | export const COMMON_TIMEZONES = [
  3 |   "UTC",
  4 |   "Europe/London",
  5 |   "Europe/Paris",
  6 |   "Europe/Berlin",
  7 |   "America/New_York",
  8 |   "America/Chicago",
  9 |   "America/Denver",
 10 |   "America/Los_Angeles",
 11 |   "Asia/Tokyo",
 12 |   "Asia/Shanghai",
 13 |   "Asia/Kolkata",
 14 |   "Australia/Sydney",
 15 |   "Pacific/Auckland"
 16 | ];
 17 | 
 18 | /**
 19 |  * Get all available timezones using the Intl API
 20 |  * @returns Array of timezone strings
 21 |  */
 22 | export function getAvailableTimezones(): string[] {
 23 |   try {
 24 |     const timezones = new Set<string>(Intl.supportedValuesOf('timeZone'));
 25 |     
 26 |     // Ensure common timezones are always included
 27 |     COMMON_TIMEZONES.forEach(tz => timezones.add(tz));
 28 |     
 29 |     return Array.from(timezones).sort();
 30 |   } catch (error) {
 31 |     console.error("Error getting timezones from Intl API:", error);
 32 |     // Fallback to common timezones if the Intl API fails
 33 |     return COMMON_TIMEZONES;
 34 |   }
 35 | }
 36 | 
 37 | /**
 38 |  * Format the list of available timezones as a string
 39 |  * @param prefix Optional prefix text to include before the list (default: "Available timezones")
 40 |  * @returns Formatted string with timezone count and comma-separated list
 41 |  */
 42 | export function getFormattedTimezoneList(prefix: string = "Available timezones"): string {
 43 |   const timezones = getAvailableTimezones();
 44 |   return `${prefix} (${timezones.length}): ${timezones.join(', ')}`;
 45 | }
 46 | 
 47 | /**
 48 |  * Check if a timezone is valid
 49 |  * @param timezone Timezone string to validate
 50 |  * @returns boolean indicating if the timezone is valid
 51 |  */
 52 | export function isValidTimezone(timezone: string): boolean {
 53 |   try {
 54 |     // Try to use the timezone with Intl.DateTimeFormat
 55 |     Intl.DateTimeFormat(undefined, { timeZone: timezone });
 56 |     return true;
 57 |   } catch (error) {
 58 |     return false;
 59 |   }
 60 | }
 61 | 
 62 | /**
 63 |  * Get the current system timezone
 64 |  * @returns The current system timezone string, or "UTC" as fallback
 65 |  */
 66 | export function getCurrentTimezone(): string {
 67 |   try {
 68 |     const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
 69 |     return processTimezone(timezone);
 70 |   } catch (error) {
 71 |     console.error("Error getting current timezone:", error);
 72 |     return "UTC"; // Default to UTC if there's an error
 73 |   }
 74 | }
 75 | 
 76 | // Export this function to make it testable
 77 | export function processTimezone(timezone: string): string {
 78 |   // Verify it's a valid timezone
 79 |   if (isValidTimezone(timezone)) {
 80 |     return timezone;
 81 |   } 
 82 |   return handleInvalidTimezone(timezone);
 83 | }
 84 | 
 85 | // Export this function to make it testable
 86 | export function handleInvalidTimezone(timezone: string): string {
 87 |   console.warn(`System timezone ${timezone} is not valid, falling back to UTC`);
 88 |   return "UTC";
 89 | }
 90 | 
 91 | /**
 92 |  * Format the current date and time for a given timezone in ISO8601 format
 93 |  * @param timezone Timezone string
 94 |  * @returns Formatted date-time string in ISO8601 format
 95 |  */
 96 | export function getCurrentTimeInTimezone(timezone: string): string {
 97 |   try {
 98 |     const date = new Date();
 99 |     
100 |     // Create a formatter that includes the timezone
101 |     const options: Intl.DateTimeFormatOptions = {
102 |       timeZone: timezone,
103 |       timeZoneName: 'short'
104 |     };
105 |     
106 |     // Get the timezone offset from the formatter
107 |     const formatter = new Intl.DateTimeFormat('en-US', options);
108 |     const formattedDate = formatter.format(date);
109 |     const timezonePart = formattedDate.split(' ').pop() || '';
110 |     
111 |     // Format the date in ISO8601 format with the timezone
112 |     // First get the date in the specified timezone
113 |     const tzFormatter = new Intl.DateTimeFormat('en-US', {
114 |       timeZone: timezone,
115 |       year: 'numeric',
116 |       month: '2-digit',
117 |       day: '2-digit',
118 |       hour: '2-digit',
119 |       minute: '2-digit',
120 |       second: '2-digit',
121 |       hour12: false,
122 |       fractionalSecondDigits: 3
123 |     });
124 |     
125 |     const parts = tzFormatter.formatToParts(date);
126 |     const dateParts: Record<string, string> = {};
127 |     
128 |     parts.forEach(part => {
129 |       if (part.type !== 'literal') {
130 |         dateParts[part.type] = part.value;
131 |       }
132 |     });
133 |     
134 |     // Format as YYYY-MM-DDTHH:MM:SS.sss±HH:MM (ISO8601)
135 |     const isoDate = `${dateParts.year}-${dateParts.month}-${dateParts.day}T${dateParts.hour}:${dateParts.minute}:${dateParts.second}.${dateParts.fractionalSecond || '000'}`;
136 |     
137 |     // For proper ISO8601, we need to add the timezone offset
138 |     // We can use the Intl.DateTimeFormat to get the timezone offset
139 |     const tzOffset = new Date().toLocaleString('en-US', { timeZone: timezone, timeZoneName: 'longOffset' }).split(' ').pop() || '';
140 |     
141 |     // Format the final ISO8601 string
142 |     return `${isoDate}${tzOffset.replace('GMT', '')}`;
143 |   } catch (error) {
144 |     console.error(`Error formatting time for timezone ${timezone}:`, error);
145 |     return 'Invalid timezone';
146 |   }
147 | } 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
  1 | {
  2 |   "compilerOptions": {
  3 |     /* Visit https://aka.ms/tsconfig to read more about this file */
  4 | 
  5 |     /* Projects */
  6 |     // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
  7 |     // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
  8 |     // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
  9 |     // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
 10 |     // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
 11 |     // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */
 12 | 
 13 |     /* Language and Environment */
 14 |     "target": "ES2022",
 15 |     // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
 16 |     // "jsx": "preserve",                                /* Specify what JSX code is generated. */
 17 |     // "libReplacement": true,                           /* Enable lib replacement. */
 18 |     // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
 19 |     // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
 20 |     // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
 21 |     // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
 22 |     // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
 23 |     // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
 24 |     // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
 25 |     // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
 26 |     // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */
 27 | 
 28 |     /* Modules */
 29 |     "module": "NodeNext",
 30 |     "moduleResolution": "NodeNext",
 31 |     // "rootDir": "./",                                  /* Specify the root folder within your source files. */
 32 |     // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
 33 |     // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
 34 |     // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
 35 |     // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
 36 |     // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
 37 |     // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
 38 |     // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
 39 |     // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
 40 |     // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
 41 |     // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
 42 |     // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
 43 |     // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
 44 |     // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
 45 |     // "resolveJsonModule": true,                        /* Enable importing .json files. */
 46 |     // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
 47 |     // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
 48 | 
 49 |     /* JavaScript Support */
 50 |     // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
 51 |     // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
 52 |     // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
 53 | 
 54 |     /* Emit */
 55 |     // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
 56 |     // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
 57 |     // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
 58 |     // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
 59 |     // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
 60 |     // "noEmit": true,                                   /* Disable emitting files from a compilation. */
 61 |     // "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
 62 |     "outDir": "./dist",
 63 |     // "removeComments": true,                           /* Disable emitting comments. */
 64 |     // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
 65 |     // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
 66 |     // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
 67 |     // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
 68 |     // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
 69 |     // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
 70 |     // "newLine": "crlf",                                /* Set the newline character for emitting files. */
 71 |     // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
 72 |     // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
 73 |     // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
 74 |     // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
 75 |     // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
 76 | 
 77 |     /* Interop Constraints */
 78 |     // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
 79 |     // "verbatimModuleSyntax": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
 80 |     // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
 81 |     // "erasableSyntaxOnly": true,                       /* Do not allow runtime constructs that are not part of ECMAScript. */
 82 |     // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
 83 |     "esModuleInterop": true,
 84 |     // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
 85 |     "forceConsistentCasingInFileNames": true,
 86 | 
 87 |     /* Type Checking */
 88 |     "strict": true,
 89 |     // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
 90 |     // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
 91 |     // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
 92 |     // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
 93 |     // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
 94 |     // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
 95 |     // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
 96 |     // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
 97 |     // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
 98 |     // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
 99 |     // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
100 |     // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
101 |     // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
102 |     // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
103 |     // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
104 |     // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
105 |     // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
106 |     // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
107 |     // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */
108 | 
109 |     /* Completeness */
110 |     // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
111 |     "skipLibCheck": true
112 |   },
113 |   "include": ["src/**/*"],
114 |   "exclude": ["node_modules"]
115 | }
116 | 
```

--------------------------------------------------------------------------------
/tests/timezone-utils.test.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
  2 | import * as tzUtils from '../src/timezone-utils.js';
  3 | import {
  4 |   getAvailableTimezones,
  5 |   getFormattedTimezoneList,
  6 |   isValidTimezone,
  7 |   getCurrentTimezone,
  8 |   getCurrentTimeInTimezone,
  9 |   handleInvalidTimezone,
 10 |   processTimezone,
 11 |   COMMON_TIMEZONES
 12 | } from '../src/timezone-utils.js';
 13 | 
 14 | describe('timezone-utils', () => {
 15 |   // Store original methods to restore after tests
 16 |   const originalSupportedValuesOf = Intl.supportedValuesOf;
 17 |   const originalDateTimeFormat = Intl.DateTimeFormat;
 18 |   const originalConsoleWarn = console.warn;
 19 |   const originalConsoleError = console.error;
 20 |   const originalToLocaleString = Date.prototype.toLocaleString;
 21 |   
 22 |   // Restore original methods after each test
 23 |   afterEach(() => {
 24 |     Intl.supportedValuesOf = originalSupportedValuesOf;
 25 |     Intl.DateTimeFormat = originalDateTimeFormat;
 26 |     console.warn = originalConsoleWarn;
 27 |     console.error = originalConsoleError;
 28 |     Date.prototype.toLocaleString = originalToLocaleString;
 29 |     
 30 |     // Restore any mocked functions
 31 |     jest.restoreAllMocks();
 32 |   });
 33 | 
 34 |   // Helper function to create a mock formatter
 35 |   const createMockFormatter = (options: {
 36 |     formattedDate?: string;
 37 |     timeZone?: string;
 38 |     timeZoneValue?: string;
 39 |     includeFractionalSecond?: boolean;
 40 |   } = {}) => {
 41 |     const {
 42 |       formattedDate = '2023-01-01T12:00:00.000+00:00',
 43 |       timeZone = 'UTC',
 44 |       timeZoneValue = 'GMT+00:00',
 45 |       includeFractionalSecond = true
 46 |     } = options;
 47 |     
 48 |     const parts = [
 49 |       { type: 'year', value: '2023' },
 50 |       { type: 'literal', value: '-' },
 51 |       { type: 'month', value: '01' },
 52 |       { type: 'literal', value: '-' },
 53 |       { type: 'day', value: '01' },
 54 |       { type: 'literal', value: 'T' },
 55 |       { type: 'hour', value: '12' },
 56 |       { type: 'literal', value: ':' },
 57 |       { type: 'minute', value: '00' },
 58 |       { type: 'literal', value: ':' },
 59 |       { type: 'second', value: '00' },
 60 |       { type: 'literal', value: '.' }
 61 |     ];
 62 |     
 63 |     if (includeFractionalSecond) {
 64 |       parts.push({ type: 'fractionalSecond', value: '000' });
 65 |     }
 66 |     
 67 |     parts.push({ type: 'timeZoneName', value: timeZoneValue });
 68 |     
 69 |     return {
 70 |       format: () => formattedDate,
 71 |       formatToParts: () => parts,
 72 |       resolvedOptions: () => ({ timeZone })
 73 |     };
 74 |   };
 75 |   
 76 |   // Helper function to mock DateTimeFormat
 77 |   const mockDateTimeFormat = (formatter: any) => {
 78 |     // @ts-ignore - TypeScript doesn't like us mocking built-in objects
 79 |     Intl.DateTimeFormat = jest.fn().mockImplementation(() => formatter);
 80 |   };
 81 |   
 82 |   describe('getAvailableTimezones', () => {
 83 |     it('should return a sorted array of timezones', () => {
 84 |       const timezones = getAvailableTimezones();
 85 |       
 86 |       // Check that we have an array of strings
 87 |       expect(Array.isArray(timezones)).toBe(true);
 88 |       expect(timezones.length).toBeGreaterThan(0);
 89 |       
 90 |       // Check that all common timezones are included
 91 |       COMMON_TIMEZONES.forEach(tz => {
 92 |         expect(timezones).toContain(tz);
 93 |       });
 94 |       
 95 |       // Check that the array is sorted
 96 |       const sortedTimezones = [...timezones].sort();
 97 |       expect(timezones).toEqual(sortedTimezones);
 98 |     });
 99 |     
100 |     it('should fall back to common timezones if Intl API fails', () => {
101 |       // Mock the Intl.supportedValuesOf to throw an error
102 |       Intl.supportedValuesOf = function() {
103 |         throw new Error('API not available');
104 |       } as any;
105 |       
106 |       const timezones = getAvailableTimezones();
107 |       
108 |       // Should fall back to common timezones
109 |       expect(timezones).toEqual(COMMON_TIMEZONES);
110 |     });
111 |   });
112 | 
113 |   describe('getFormattedTimezoneList', () => {
114 |     it('should format the timezone list with default prefix', () => {
115 |       const timezones = getAvailableTimezones();
116 |       const formattedList = getFormattedTimezoneList();
117 |       
118 |       expect(formattedList).toContain('Available timezones');
119 |       expect(formattedList).toContain(`(${timezones.length})`);
120 |     });
121 | 
122 |     it('should format the timezone list with custom prefix', () => {
123 |       const timezones = getAvailableTimezones();
124 |       const customPrefix = 'Custom prefix';
125 |       const formattedList = getFormattedTimezoneList(customPrefix);
126 |       
127 |       expect(formattedList).toContain(customPrefix);
128 |       expect(formattedList).toContain(`(${timezones.length})`);
129 |     });
130 |   });
131 | 
132 |   describe('isValidTimezone', () => {
133 |     it('should return true for valid timezones', () => {
134 |       expect(isValidTimezone('UTC')).toBe(true);
135 |       expect(isValidTimezone('Europe/London')).toBe(true);
136 |     });
137 | 
138 |     it('should return false for invalid timezones', () => {
139 |       expect(isValidTimezone('Invalid/Timezone')).toBe(false);
140 |       expect(isValidTimezone('')).toBe(false);
141 |     });
142 |   });
143 | 
144 |   describe('getCurrentTimezone', () => {
145 |     beforeEach(() => {
146 |       console.warn = jest.fn();
147 |       console.error = jest.fn();
148 |     });
149 | 
150 |     it('should return the current timezone', () => {
151 |       const timezone = getCurrentTimezone();
152 |       expect(typeof timezone).toBe('string');
153 |       expect(timezone.length).toBeGreaterThan(0);
154 |     });
155 | 
156 |     it('should fall back to UTC if there is an error', () => {
157 |       // Mock Intl.DateTimeFormat to throw an error
158 |       Intl.DateTimeFormat = jest.fn().mockImplementation(() => {
159 |         throw new Error('API error');
160 |       }) as any;
161 | 
162 |       const timezone = getCurrentTimezone();
163 |       expect(timezone).toBe('UTC');
164 |       expect(console.error).toHaveBeenCalledWith(
165 |         'Error getting current timezone:',
166 |         expect.any(Error)
167 |       );
168 |     });
169 | 
170 |     it('should log a warning and fall back to UTC for invalid timezones', () => {
171 |       const invalidTimezone = 'Invalid/Timezone';
172 |       const result = handleInvalidTimezone(invalidTimezone);
173 |       
174 |       expect(result).toBe('UTC');
175 |       expect(console.warn).toHaveBeenCalledWith(
176 |         `System timezone ${invalidTimezone} is not valid, falling back to UTC`
177 |       );
178 |     });
179 | 
180 |     it('should call handleInvalidTimezone for invalid system timezone', () => {
181 |       // Create a function that simulates getCurrentTimezone with an invalid timezone
182 |       const simulateGetCurrentTimezoneWithInvalidTimezone = () => {
183 |         try {
184 |           const timezone = 'Invalid/Timezone';
185 |           if (isValidTimezone(timezone)) {
186 |             return timezone;
187 |           } else {
188 |             return handleInvalidTimezone(timezone);
189 |           }
190 |         } catch (error) {
191 |           console.error("Error getting current timezone:", error);
192 |           return "UTC";
193 |         }
194 |       };
195 | 
196 |       const timezone = simulateGetCurrentTimezoneWithInvalidTimezone();
197 |       expect(timezone).toBe('UTC');
198 |       expect(console.warn).toHaveBeenCalledWith(
199 |         'System timezone Invalid/Timezone is not valid, falling back to UTC'
200 |       );
201 |     });
202 |   });
203 | 
204 |   describe('getCurrentTimeInTimezone', () => {
205 |     beforeEach(() => {
206 |       console.error = jest.fn();
207 |     });
208 | 
209 |     it('should format the current time in UTC', () => {
210 |       // Mock the DateTimeFormat constructor with our helper
211 |       mockDateTimeFormat(createMockFormatter());
212 |       
213 |       const time = getCurrentTimeInTimezone('UTC');
214 |       
215 |       // Check that it returns a string
216 |       expect(typeof time).toBe('string');
217 |       
218 |       // Check that it's not the error message
219 |       expect(time).not.toBe('Invalid timezone');
220 |     });
221 | 
222 |     it('should format the current time in a specific timezone', () => {
223 |       // Mock the DateTimeFormat constructor with our helper
224 |       mockDateTimeFormat(createMockFormatter({
225 |         formattedDate: '2023-01-01T12:00:00.000+01:00',
226 |         timeZone: 'Europe/London',
227 |         timeZoneValue: 'GMT+01:00'
228 |       }));
229 |       
230 |       const time = getCurrentTimeInTimezone('Europe/London');
231 |       
232 |       // Check that it returns a string
233 |       expect(typeof time).toBe('string');
234 |       
235 |       // Check that it's not the error message
236 |       expect(time).not.toBe('Invalid timezone');
237 |     });
238 | 
239 |     it('should return an error message for invalid timezones', () => {
240 |       const time = getCurrentTimeInTimezone('Invalid/Timezone');
241 |       expect(time).toBe('Invalid timezone');
242 |     });
243 | 
244 |     it('should handle edge cases in date formatting', () => {
245 |       // Mock DateTimeFormat to throw an error
246 |       Intl.DateTimeFormat = jest.fn().mockImplementation(() => {
247 |         throw new Error('Mock error');
248 |       }) as any;
249 |       
250 |       // This should trigger the catch block in getCurrentTimeInTimezone
251 |       const result = getCurrentTimeInTimezone('UTC');
252 |       
253 |       // Verify we got the error message
254 |       expect(result).toBe('Invalid timezone');
255 |       
256 |       // Verify console.error was called
257 |       expect(console.error).toHaveBeenCalled();
258 |     });
259 | 
260 |     it('should handle empty timezone parts in formatting', () => {
261 |       // First mock for the formatter that gets the timezone offset
262 |       const mockEmptyFormatter = {
263 |         format: jest.fn().mockReturnValue('2023-01-01') // No timezone part
264 |       };
265 |       
266 |       // Second mock for the formatter that gets the date parts
267 |       const mockTzFormatter = {
268 |         formatToParts: jest.fn().mockReturnValue([
269 |           { type: 'year', value: '2023' },
270 |           { type: 'month', value: '01' },
271 |           { type: 'day', value: '01' },
272 |           { type: 'hour', value: '12' },
273 |           { type: 'minute', value: '00' },
274 |           { type: 'second', value: '00' }
275 |           // No fractionalSecond to test that case
276 |         ])
277 |       };
278 |       
279 |       // Mock Date.toLocaleString to return a string without timezone
280 |       Date.prototype.toLocaleString = jest.fn().mockReturnValue('January 1, 2023') as any;
281 |       
282 |       // Mock DateTimeFormat to return our formatters
283 |       Intl.DateTimeFormat = jest.fn()
284 |         .mockImplementationOnce(() => mockEmptyFormatter)
285 |         .mockImplementationOnce(() => mockTzFormatter) as any;
286 |       
287 |       const result = getCurrentTimeInTimezone('UTC');
288 |       
289 |       // Verify the result contains the expected date format
290 |       expect(result).toContain('2023-01-01T12:00:00.000');
291 |     });
292 | 
293 |     it('should handle null values in split operations', () => {
294 |       // Mock formatter with null format result
295 |       const mockNullFormatter = {
296 |         format: jest.fn().mockReturnValue(null)
297 |       };
298 |       
299 |       // Mock DateTimeFormat to return our formatter
300 |       Intl.DateTimeFormat = jest.fn().mockImplementation(() => mockNullFormatter) as any;
301 |       
302 |       const result = getCurrentTimeInTimezone('UTC');
303 |       
304 |       // The function should handle the null values and return an error
305 |       expect(result).toBe('Invalid timezone');
306 |       
307 |       // Verify console.error was called
308 |       expect(console.error).toHaveBeenCalled();
309 |     });
310 | 
311 |     it('should handle empty result from formatter.format()', () => {
312 |       // Mock for the formatter that returns empty string
313 |       const mockEmptyFormatter = {
314 |         format: jest.fn().mockReturnValue('')
315 |       };
316 |       
317 |       // Mock for the formatter that gets the date parts
318 |       const mockTzFormatter = {
319 |         formatToParts: jest.fn().mockReturnValue([
320 |           { type: 'year', value: '2023' },
321 |           { type: 'month', value: '01' },
322 |           { type: 'day', value: '01' },
323 |           { type: 'hour', value: '12' },
324 |           { type: 'minute', value: '00' },
325 |           { type: 'second', value: '00' },
326 |           { type: 'fractionalSecond', value: '123' }
327 |         ])
328 |       };
329 |       
330 |       // Mock Date.toLocaleString to return a valid string
331 |       Date.prototype.toLocaleString = jest.fn().mockReturnValue('1/1/2023, 12:00:00 PM GMT+0000') as any;
332 |       
333 |       // Mock DateTimeFormat to return our formatters
334 |       Intl.DateTimeFormat = jest.fn()
335 |         .mockImplementationOnce(() => mockEmptyFormatter)
336 |         .mockImplementationOnce(() => mockTzFormatter) as any;
337 |       
338 |       const result = getCurrentTimeInTimezone('UTC');
339 |       
340 |       // The function should handle the empty string and still return a result
341 |       expect(result).toContain('2023-01-01T12:00:00.123');
342 |     });
343 | 
344 |     it('should handle empty result from toLocaleString()', () => {
345 |       // Mock for the formatter that returns valid string
346 |       const mockFormatter = {
347 |         format: jest.fn().mockReturnValue('1/1/2023, 12:00:00 PM GMT+0000')
348 |       };
349 |       
350 |       // Mock for the formatter that gets the date parts
351 |       const mockTzFormatter = {
352 |         formatToParts: jest.fn().mockReturnValue([
353 |           { type: 'year', value: '2023' },
354 |           { type: 'month', value: '01' },
355 |           { type: 'day', value: '01' },
356 |           { type: 'hour', value: '12' },
357 |           { type: 'minute', value: '00' },
358 |           { type: 'second', value: '00' },
359 |           { type: 'fractionalSecond', value: '123' }
360 |         ])
361 |       };
362 |       
363 |       // Mock Date.toLocaleString to return an empty string
364 |       Date.prototype.toLocaleString = jest.fn().mockReturnValue('') as any;
365 |       
366 |       // Mock DateTimeFormat to return our formatters
367 |       Intl.DateTimeFormat = jest.fn()
368 |         .mockImplementationOnce(() => mockFormatter)
369 |         .mockImplementationOnce(() => mockTzFormatter) as any;
370 |       
371 |       const result = getCurrentTimeInTimezone('UTC');
372 |       
373 |       // The function should handle the empty string and still return a result
374 |       expect(result).toContain('2023-01-01T12:00:00.123');
375 |     });
376 |   });
377 | 
378 |   describe('handleInvalidTimezone', () => {
379 |     beforeEach(() => {
380 |       console.warn = jest.fn();
381 |     });
382 | 
383 |     it('should log a warning and return UTC', () => {
384 |       const invalidTimezone = 'Invalid/Timezone';
385 |       const result = handleInvalidTimezone(invalidTimezone);
386 |       
387 |       expect(result).toBe('UTC');
388 |       expect(console.warn).toHaveBeenCalledWith(
389 |         `System timezone ${invalidTimezone} is not valid, falling back to UTC`
390 |       );
391 |     });
392 | 
393 |     it('should be called when isValidTimezone returns false', () => {
394 |       // Create a test function that simulates the exact code path
395 |       const testInvalidTimezone = (timezone: string) => {
396 |         if (isValidTimezone(timezone)) {
397 |           return timezone;
398 |         }
399 |         return handleInvalidTimezone(timezone);
400 |       };
401 | 
402 |       // Use a timezone that we know is invalid
403 |       const result = testInvalidTimezone('Invalid/Timezone');
404 |       
405 |       expect(result).toBe('UTC');
406 |       expect(console.warn).toHaveBeenCalledWith(
407 |         'System timezone Invalid/Timezone is not valid, falling back to UTC'
408 |       );
409 |     });
410 |   });
411 | 
412 |   describe('processTimezone', () => {
413 |     beforeEach(() => {
414 |       console.warn = jest.fn();
415 |     });
416 | 
417 |     it('should return the timezone if it is valid', () => {
418 |       const validTimezone = 'UTC';
419 |       const result = processTimezone(validTimezone);
420 |       expect(result).toBe(validTimezone);
421 |       expect(console.warn).not.toHaveBeenCalled();
422 |     });
423 | 
424 |     it('should call handleInvalidTimezone for invalid timezones', () => {
425 |       const invalidTimezone = 'Invalid/Timezone';
426 |       const result = processTimezone(invalidTimezone);
427 |       expect(result).toBe('UTC');
428 |       expect(console.warn).toHaveBeenCalledWith(
429 |         `System timezone ${invalidTimezone} is not valid, falling back to UTC`
430 |       );
431 |     });
432 |   });
433 | });
```