#
tokens: 10608/50000 10/10 files
lines: off (toggle) GitHub
raw markdown copy
# 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:
--------------------------------------------------------------------------------

```
# Dependency directories
node_modules/

# Build output
dist/

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local

# Editor directories and files
.idea/
.vscode/
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# OS specific
.DS_Store
Thumbs.db 

# Test coverage
coverage/
.nyc_output/ 
```

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

```markdown
# MCP DateTime

A TypeScript implementation of a Model Context Protocol (MCP) server that provides datetime and timezone information to agentic systems and chat REPLs.

## Overview

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:

- Get the current time in the local system timezone
- Get the current time in any valid timezone
- List all available timezones
- Access timezone information through URI resources

## Installation

### From npm

```bash
npm install -g mcp-datetime
```

### From source

```bash
git clone https://github.com/odgrmi/mcp-datetime.git
cd mcp-datetime
npm install
npm run build
```

## Usage

### Command Line

MCP DateTime can be run in two modes:

#### 1. Standard I/O Mode (Default)

This mode is ideal for integrating with AI systems that support the MCP protocol through standard input/output:

```bash
mcp-datetime
```

#### 2. Server-Sent Events (SSE) Mode

This mode starts an HTTP server that provides SSE transport for the MCP protocol:

```bash
mcp-datetime --sse
```

You can also specify a custom port and URI prefix:

```bash
mcp-datetime --sse --port=8080 --prefix=/api/datetime
```

### Environment Variables

- `PORT`: Sets the port for SSE mode (default: 3000)
- `URI_PREFIX`: Sets the URI prefix for SSE mode (default: none)

## Available Tools

MCP DateTime provides the following tools:

### `get-current-time`

Returns the current time in the system's local timezone.

### `get-current-timezone`

Returns the current system timezone.

### `get-time-in-timezone`

Returns the current time in a specified timezone.

Parameters:
- `timezone`: The timezone to get the current time for (e.g., "America/New_York")

### `list-timezones`

Returns a list of all available timezones.

## Resource URIs

MCP DateTime also provides access to timezone information through resource URIs:

### `datetime://{timezone}`

Returns the current time in the specified timezone.

Example: `datetime://America/New_York`

### `datetime://list`

Returns a list of all available timezones.

## Common Timezones

The following common timezones are always available:

- UTC
- Europe/London
- Europe/Paris
- Europe/Berlin
- America/New_York
- America/Chicago
- America/Denver
- America/Los_Angeles
- Asia/Tokyo
- Asia/Shanghai
- Asia/Kolkata
- Australia/Sydney
- Pacific/Auckland

## SSE Endpoints

When running in SSE mode, the following endpoints are available:

- `/sse`: SSE connection endpoint
- `/message`: Message endpoint for client-to-server communication
- `/info`: Basic server information

If a URI prefix is specified, it will be prepended to all endpoints.

## Integration with AI Systems

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.

## Development

### Prerequisites

- Node.js 14.16 or higher
- npm

### Setup

```bash
git clone https://github.com/odgrim/mcp-datetime.git
cd mcp-datetime
npm install
```

### Build

```bash
npm run build
```

### Run in Development Mode

```bash
npm run dev        # Standard I/O mode
npm run dev:sse    # SSE mode
```

## License

This project is licensed under the Mozilla Public License 2.0 - see the [LICENSE](LICENSE) file for details. 

```

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

```javascript
export default {
  preset: 'ts-jest',
  testEnvironment: 'node',
  extensionsToTreatAsEsm: ['.ts'],
  moduleNameMapper: {
    '^(\\.{1,2}/.*)\\.js$': '$1',
  },
  transform: {
    '^.+\\.tsx?$': [
      'ts-jest',
      {
        useESM: true,
      },
    ],
  },
  collectCoverageFrom: [
    'src/**/*.ts',
    '!src/**/*.d.ts',
    '!src/**/index.ts',
  ],
  coverageReporters: ['text', 'lcov', 'clover', 'html'],
  coverageDirectory: 'coverage',
}; 
```

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

```json
{
  "name": "@odgrim/mcp-datetime",
  "version": "0.2.0",
  "description": "A TypeScript implementation of a simple MCP server that exposes datetime information to agentic systems and chat REPLs",
  "type": "module",
  "main": "dist/index.js",
  "bin": {
    "mcp-datetime": "dist/index.js"
  },
  "files": [
    "dist"
  ],
  "scripts": {
    "build": "tsc && shx chmod +x dist/*.js",
    "prepare": "npm run build",
    "watch": "tsc --watch",
    "start": "node dist/index.js",
    "start:sse": "node dist/index.js --sse",
    "dev": "ts-node --esm src/index.ts",
    "dev:sse": "ts-node --esm src/index.ts --sse",
    "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
    "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage"
  },
  "keywords": [
    "mcp",
    "datetime",
    "timezone",
    "model-context-protocol"
  ],
  "author": "odgrim",
  "license": "MPL-2.0",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.4.1",
    "@types/express": "^5.0.0",
    "express": "^4.21.2",
    "zod": "^3.22.4"
  },
  "devDependencies": {
    "@types/jest": "^29.5.14",
    "@types/node": "^20.11.24",
    "jest": "^29.7.0",
    "shx": "^0.3.4",
    "ts-jest": "^29.2.6",
    "ts-node": "^10.9.2",
    "typescript": "^5.3.3"
  },
  "engines": {
    "node": ">=14.16"
  },
  "publishConfig": {
    "access": "public"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/odgrim/mcp-datetime.git"
  },
  "bugs": {
    "url": "https://github.com/odgrim/mcp-datetime/issues"
  },
  "homepage": "https://github.com/odgrim/mcp-datetime#readme"
}

```

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

```typescript
#!/usr/bin/env node

import { server } from "./server.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { startSSEServer } from "./sse.js";

async function main() {
  console.error("Starting MCP DateTime server...");
  
  // Check if the --sse flag is provided
  const useSSE = process.argv.includes("--sse");
  
  // Check if a custom port is provided with --port=XXXX
  const portArg = process.argv.find(arg => arg.startsWith("--port="));
  // Use PORT environment variable or command line argument or default to 3000
  const defaultPort = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000;
  const port = portArg ? parseInt(portArg.split("=")[1], 10) : defaultPort;
  
  // Check if a URI prefix is provided with --prefix=XXXX
  const prefixArg = process.argv.find(arg => arg.startsWith("--prefix="));
  // Use URI_PREFIX environment variable or command line argument or default to empty string
  const defaultPrefix = process.env.URI_PREFIX || "";
  const uriPrefix = prefixArg ? prefixArg.split("=")[1] : defaultPrefix;

  try {
    if (useSSE) {
      // Start the SSE server
      console.error(`Starting MCP DateTime server with SSE transport on port ${port}...`);
      const cleanup = startSSEServer(port, uriPrefix);
      
      // Handle graceful shutdown
      process.on("SIGINT", async () => {
        console.error("Received SIGINT, shutting down...");
        await cleanup();
        process.exit(0);
      });
      
      process.on("SIGTERM", async () => {
        console.error("Received SIGTERM, shutting down...");
        await cleanup();
        process.exit(0);
      });
    } else {
      // Connect to stdio transport
      await server.connect(new StdioServerTransport());
      console.error("MCP DateTime server connected to stdio transport");
    }
  } catch (error) {
    console.error("Error starting MCP DateTime server:", error);
    process.exit(1);
  }
}

main().catch(error => {
  console.error("Unhandled error:", error);
  process.exit(1);
}); 
```

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

```typescript
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
import express from "express";
import { server } from "./server.js";

/**
 * Creates and starts an Express server that provides SSE transport for the MCP DateTime server
 * @param port The port to listen on (defaults to PORT env var or 3000)
 * @param uriPrefix The URI prefix to prepend to all routes (for reverse proxy scenarios)
 * @returns A cleanup function to close the server
 */
export function startSSEServer(
  port: number = process.env.PORT ? parseInt(process.env.PORT, 10) : 3000,
  uriPrefix: string = ""
): () => Promise<void> {
  const app = express();
  let transport: SSEServerTransport;

  // Normalize the URI prefix to ensure it starts with a / and doesn't end with one
  const normalizedPrefix = uriPrefix 
    ? (uriPrefix.startsWith('/') ? uriPrefix : `/${uriPrefix}`).replace(/\/+$/, '')
    : '';
  
  // Define the endpoint paths with the prefix
  const ssePath = `${normalizedPrefix}/sse`;
  const messagePath = `${normalizedPrefix}/message`;
  const infoPath = `${normalizedPrefix}/info`;

  // SSE endpoint for establishing a connection
  app.get(ssePath, async (req, res) => {
    console.error("Received SSE connection");
    transport = new SSEServerTransport(messagePath, res);
    await server.connect(transport);
  });

  // Endpoint for receiving messages from the client
  app.post(messagePath, async (req, res) => {
    console.error("Received message");
    await transport.handlePostMessage(req, res);
  });

  // Basic info endpoint
  app.get(infoPath, (req, res) => {
    res.json({
      name: "MCP DateTime Server",
      version: "0.1.0",
      transport: "SSE",
      endpoints: {
        sse: ssePath,
        message: messagePath,
        info: infoPath
      }
    });
  });

  // Start the server
  const httpServer = app.listen(port, () => {
    console.error(`MCP DateTime server listening on port ${port}`);
    console.error(`URI prefix: ${normalizedPrefix || '/'} (root)`);
    console.error(`SSE endpoint: http://localhost:${port}${ssePath}`);
    console.error(`Message endpoint: http://localhost:${port}${messagePath}`);
    console.error(`Info endpoint: http://localhost:${port}${infoPath}`);
  });

  // Return a cleanup function
  return async () => {
    console.error("Closing SSE server...");
    httpServer.close();
    if (transport) {
      await server.close();
    }
  };
} 
```

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

```typescript
import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { 
  COMMON_TIMEZONES, 
  getAvailableTimezones, 
  isValidTimezone, 
  getCurrentTimeInTimezone, 
  getCurrentTimezone,
  getFormattedTimezoneList
} from "./timezone-utils.js";

// Create an MCP server
export const server = new McpServer({
  name: "mcp-datetime",
  version: "0.1.0"
});

// Add a tool to get the current time in the local timezone
server.tool(
  "get-current-time",
  "Get the current time in the configured local timezone",
  async () => ({
    content: [{ 
      type: "text", 
      text: `The current time is ${getCurrentTimeInTimezone(getCurrentTimezone())}`
    }]
  })
);

// Add a tool to get the current system timezone
server.tool(
  "get-current-timezone",
  "Get the current system timezone",
  async () => {
    const timezone = getCurrentTimezone();
    return {
      content: [{ 
        type: "text", 
        text: `The current system timezone is ${timezone}`
      }]
    };
  }
);

// Add a tool to get the current time in a specific timezone
server.tool(
  "get-time-in-timezone",
  "Get the current time in a specific timezone",
  {
    timezone: z.string().describe("The timezone to get the current time for")
  },
  async (args) => {
    if (!isValidTimezone(args.timezone)) {
      return {
        content: [{ 
          type: "text", 
          text: `Error: Invalid timezone "${args.timezone}". Use the "list-timezones" tool to see available options.`
        }],
        isError: true
      };
    }
    
    return {
      content: [{ 
        type: "text", 
        text: `The current time in ${args.timezone} is ${getCurrentTimeInTimezone(args.timezone)}`
      }]
    };
  }
);

// Add a tool to list all available timezones
server.tool(
  "list-timezones",
  "List all available timezones",
  async () => {
    return {
      content: [{ 
        type: "text", 
        text: getFormattedTimezoneList()
      }]
    };
  }
);

// Create a resource template for datetime URIs
// The list method is defined to return a list of common timezones
// to avoid overwhelming the client with all available timezones
const datetimeTemplate = new ResourceTemplate("datetime://{timezone}", {
  list: async () => {
    return {
      resources: COMMON_TIMEZONES.map(timezone => ({
        uri: `datetime://${encodeURIComponent(timezone)}`,
        name: `Current time in ${timezone}`,
        description: `Get the current time in the ${timezone} timezone`,
        mimeType: "text/plain"
      }))
    };
  }
});

// Register the template with the server
server.resource(
  "datetime-template",
  datetimeTemplate,
  async (uri, variables) => {
    // Decode the timezone from the URI
    const encodedTimezone = variables.timezone as string;
    const timezone = decodeURIComponent(encodedTimezone);
    
    if (!timezone || !isValidTimezone(timezone)) {
      throw new Error(`Invalid timezone: ${timezone}`);
    }
    
    const formattedTime = getCurrentTimeInTimezone(timezone);
    return {
      contents: [{
        uri: decodeURIComponent(uri.href),
        text: `Current time in ${timezone}: ${formattedTime}`,
        mimeType: "text/plain"
      }]
    };
  }
);

// Add a resource to list all available timezones
server.resource(
  "datetime-list",
  "datetime://list",
  async () => {
    return {
      contents: [{
        uri: "datetime://list",
        text: getFormattedTimezoneList("All available timezones"),
        mimeType: "text/plain"
      }]
    };
  }
); 
```

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

```typescript
// Common timezones to ensure they're always available
export const COMMON_TIMEZONES = [
  "UTC",
  "Europe/London",
  "Europe/Paris",
  "Europe/Berlin",
  "America/New_York",
  "America/Chicago",
  "America/Denver",
  "America/Los_Angeles",
  "Asia/Tokyo",
  "Asia/Shanghai",
  "Asia/Kolkata",
  "Australia/Sydney",
  "Pacific/Auckland"
];

/**
 * Get all available timezones using the Intl API
 * @returns Array of timezone strings
 */
export function getAvailableTimezones(): string[] {
  try {
    const timezones = new Set<string>(Intl.supportedValuesOf('timeZone'));
    
    // Ensure common timezones are always included
    COMMON_TIMEZONES.forEach(tz => timezones.add(tz));
    
    return Array.from(timezones).sort();
  } catch (error) {
    console.error("Error getting timezones from Intl API:", error);
    // Fallback to common timezones if the Intl API fails
    return COMMON_TIMEZONES;
  }
}

/**
 * Format the list of available timezones as a string
 * @param prefix Optional prefix text to include before the list (default: "Available timezones")
 * @returns Formatted string with timezone count and comma-separated list
 */
export function getFormattedTimezoneList(prefix: string = "Available timezones"): string {
  const timezones = getAvailableTimezones();
  return `${prefix} (${timezones.length}): ${timezones.join(', ')}`;
}

/**
 * Check if a timezone is valid
 * @param timezone Timezone string to validate
 * @returns boolean indicating if the timezone is valid
 */
export function isValidTimezone(timezone: string): boolean {
  try {
    // Try to use the timezone with Intl.DateTimeFormat
    Intl.DateTimeFormat(undefined, { timeZone: timezone });
    return true;
  } catch (error) {
    return false;
  }
}

/**
 * Get the current system timezone
 * @returns The current system timezone string, or "UTC" as fallback
 */
export function getCurrentTimezone(): string {
  try {
    const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
    return processTimezone(timezone);
  } catch (error) {
    console.error("Error getting current timezone:", error);
    return "UTC"; // Default to UTC if there's an error
  }
}

// Export this function to make it testable
export function processTimezone(timezone: string): string {
  // Verify it's a valid timezone
  if (isValidTimezone(timezone)) {
    return timezone;
  } 
  return handleInvalidTimezone(timezone);
}

// Export this function to make it testable
export function handleInvalidTimezone(timezone: string): string {
  console.warn(`System timezone ${timezone} is not valid, falling back to UTC`);
  return "UTC";
}

/**
 * Format the current date and time for a given timezone in ISO8601 format
 * @param timezone Timezone string
 * @returns Formatted date-time string in ISO8601 format
 */
export function getCurrentTimeInTimezone(timezone: string): string {
  try {
    const date = new Date();
    
    // Create a formatter that includes the timezone
    const options: Intl.DateTimeFormatOptions = {
      timeZone: timezone,
      timeZoneName: 'short'
    };
    
    // Get the timezone offset from the formatter
    const formatter = new Intl.DateTimeFormat('en-US', options);
    const formattedDate = formatter.format(date);
    const timezonePart = formattedDate.split(' ').pop() || '';
    
    // Format the date in ISO8601 format with the timezone
    // First get the date in the specified timezone
    const tzFormatter = new Intl.DateTimeFormat('en-US', {
      timeZone: timezone,
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
      second: '2-digit',
      hour12: false,
      fractionalSecondDigits: 3
    });
    
    const parts = tzFormatter.formatToParts(date);
    const dateParts: Record<string, string> = {};
    
    parts.forEach(part => {
      if (part.type !== 'literal') {
        dateParts[part.type] = part.value;
      }
    });
    
    // Format as YYYY-MM-DDTHH:MM:SS.sss±HH:MM (ISO8601)
    const isoDate = `${dateParts.year}-${dateParts.month}-${dateParts.day}T${dateParts.hour}:${dateParts.minute}:${dateParts.second}.${dateParts.fractionalSecond || '000'}`;
    
    // For proper ISO8601, we need to add the timezone offset
    // We can use the Intl.DateTimeFormat to get the timezone offset
    const tzOffset = new Date().toLocaleString('en-US', { timeZone: timezone, timeZoneName: 'longOffset' }).split(' ').pop() || '';
    
    // Format the final ISO8601 string
    return `${isoDate}${tzOffset.replace('GMT', '')}`;
  } catch (error) {
    console.error(`Error formatting time for timezone ${timezone}:`, error);
    return 'Invalid timezone';
  }
} 
```

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

```json
{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */

    /* Projects */
    // "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
    // "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
    // "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
    // "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
    // "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
    // "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

    /* Language and Environment */
    "target": "ES2022",
    // "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
    // "jsx": "preserve",                                /* Specify what JSX code is generated. */
    // "libReplacement": true,                           /* Enable lib replacement. */
    // "experimentalDecorators": true,                   /* Enable experimental support for legacy experimental decorators. */
    // "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
    // "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
    // "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
    // "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
    // "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
    // "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
    // "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
    // "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */

    /* Modules */
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    // "rootDir": "./",                                  /* Specify the root folder within your source files. */
    // "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
    // "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
    // "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
    // "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
    // "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
    // "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
    // "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
    // "allowImportingTsExtensions": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
    // "rewriteRelativeImportExtensions": true,          /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */
    // "resolvePackageJsonExports": true,                /* Use the package.json 'exports' field when resolving package imports. */
    // "resolvePackageJsonImports": true,                /* Use the package.json 'imports' field when resolving imports. */
    // "customConditions": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
    // "noUncheckedSideEffectImports": true,             /* Check side effect imports. */
    // "resolveJsonModule": true,                        /* Enable importing .json files. */
    // "allowArbitraryExtensions": true,                 /* Enable importing files with any extension, provided a declaration file is present. */
    // "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

    /* JavaScript Support */
    // "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
    // "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
    // "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

    /* Emit */
    // "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
    // "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
    // "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
    // "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
    // "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
    // "noEmit": true,                                   /* Disable emitting files from a compilation. */
    // "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. */
    "outDir": "./dist",
    // "removeComments": true,                           /* Disable emitting comments. */
    // "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
    // "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
    // "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
    // "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
    // "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
    // "newLine": "crlf",                                /* Set the newline character for emitting files. */
    // "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
    // "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
    // "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
    // "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
    // "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */

    /* Interop Constraints */
    // "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
    // "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. */
    // "isolatedDeclarations": true,                     /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
    // "erasableSyntaxOnly": true,                       /* Do not allow runtime constructs that are not part of ECMAScript. */
    // "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
    "esModuleInterop": true,
    // "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
    "forceConsistentCasingInFileNames": true,

    /* Type Checking */
    "strict": true,
    // "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
    // "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
    // "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
    // "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
    // "strictBuiltinIteratorReturn": true,              /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */
    // "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
    // "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
    // "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
    // "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
    // "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
    // "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
    // "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
    // "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
    // "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
    // "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
    // "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
    // "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
    // "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

    /* Completeness */
    // "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

```

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

```typescript
import { jest, describe, it, expect, beforeEach, afterEach } from '@jest/globals';
import * as tzUtils from '../src/timezone-utils.js';
import {
  getAvailableTimezones,
  getFormattedTimezoneList,
  isValidTimezone,
  getCurrentTimezone,
  getCurrentTimeInTimezone,
  handleInvalidTimezone,
  processTimezone,
  COMMON_TIMEZONES
} from '../src/timezone-utils.js';

describe('timezone-utils', () => {
  // Store original methods to restore after tests
  const originalSupportedValuesOf = Intl.supportedValuesOf;
  const originalDateTimeFormat = Intl.DateTimeFormat;
  const originalConsoleWarn = console.warn;
  const originalConsoleError = console.error;
  const originalToLocaleString = Date.prototype.toLocaleString;
  
  // Restore original methods after each test
  afterEach(() => {
    Intl.supportedValuesOf = originalSupportedValuesOf;
    Intl.DateTimeFormat = originalDateTimeFormat;
    console.warn = originalConsoleWarn;
    console.error = originalConsoleError;
    Date.prototype.toLocaleString = originalToLocaleString;
    
    // Restore any mocked functions
    jest.restoreAllMocks();
  });

  // Helper function to create a mock formatter
  const createMockFormatter = (options: {
    formattedDate?: string;
    timeZone?: string;
    timeZoneValue?: string;
    includeFractionalSecond?: boolean;
  } = {}) => {
    const {
      formattedDate = '2023-01-01T12:00:00.000+00:00',
      timeZone = 'UTC',
      timeZoneValue = 'GMT+00:00',
      includeFractionalSecond = true
    } = options;
    
    const parts = [
      { type: 'year', value: '2023' },
      { type: 'literal', value: '-' },
      { type: 'month', value: '01' },
      { type: 'literal', value: '-' },
      { type: 'day', value: '01' },
      { type: 'literal', value: 'T' },
      { type: 'hour', value: '12' },
      { type: 'literal', value: ':' },
      { type: 'minute', value: '00' },
      { type: 'literal', value: ':' },
      { type: 'second', value: '00' },
      { type: 'literal', value: '.' }
    ];
    
    if (includeFractionalSecond) {
      parts.push({ type: 'fractionalSecond', value: '000' });
    }
    
    parts.push({ type: 'timeZoneName', value: timeZoneValue });
    
    return {
      format: () => formattedDate,
      formatToParts: () => parts,
      resolvedOptions: () => ({ timeZone })
    };
  };
  
  // Helper function to mock DateTimeFormat
  const mockDateTimeFormat = (formatter: any) => {
    // @ts-ignore - TypeScript doesn't like us mocking built-in objects
    Intl.DateTimeFormat = jest.fn().mockImplementation(() => formatter);
  };
  
  describe('getAvailableTimezones', () => {
    it('should return a sorted array of timezones', () => {
      const timezones = getAvailableTimezones();
      
      // Check that we have an array of strings
      expect(Array.isArray(timezones)).toBe(true);
      expect(timezones.length).toBeGreaterThan(0);
      
      // Check that all common timezones are included
      COMMON_TIMEZONES.forEach(tz => {
        expect(timezones).toContain(tz);
      });
      
      // Check that the array is sorted
      const sortedTimezones = [...timezones].sort();
      expect(timezones).toEqual(sortedTimezones);
    });
    
    it('should fall back to common timezones if Intl API fails', () => {
      // Mock the Intl.supportedValuesOf to throw an error
      Intl.supportedValuesOf = function() {
        throw new Error('API not available');
      } as any;
      
      const timezones = getAvailableTimezones();
      
      // Should fall back to common timezones
      expect(timezones).toEqual(COMMON_TIMEZONES);
    });
  });

  describe('getFormattedTimezoneList', () => {
    it('should format the timezone list with default prefix', () => {
      const timezones = getAvailableTimezones();
      const formattedList = getFormattedTimezoneList();
      
      expect(formattedList).toContain('Available timezones');
      expect(formattedList).toContain(`(${timezones.length})`);
    });

    it('should format the timezone list with custom prefix', () => {
      const timezones = getAvailableTimezones();
      const customPrefix = 'Custom prefix';
      const formattedList = getFormattedTimezoneList(customPrefix);
      
      expect(formattedList).toContain(customPrefix);
      expect(formattedList).toContain(`(${timezones.length})`);
    });
  });

  describe('isValidTimezone', () => {
    it('should return true for valid timezones', () => {
      expect(isValidTimezone('UTC')).toBe(true);
      expect(isValidTimezone('Europe/London')).toBe(true);
    });

    it('should return false for invalid timezones', () => {
      expect(isValidTimezone('Invalid/Timezone')).toBe(false);
      expect(isValidTimezone('')).toBe(false);
    });
  });

  describe('getCurrentTimezone', () => {
    beforeEach(() => {
      console.warn = jest.fn();
      console.error = jest.fn();
    });

    it('should return the current timezone', () => {
      const timezone = getCurrentTimezone();
      expect(typeof timezone).toBe('string');
      expect(timezone.length).toBeGreaterThan(0);
    });

    it('should fall back to UTC if there is an error', () => {
      // Mock Intl.DateTimeFormat to throw an error
      Intl.DateTimeFormat = jest.fn().mockImplementation(() => {
        throw new Error('API error');
      }) as any;

      const timezone = getCurrentTimezone();
      expect(timezone).toBe('UTC');
      expect(console.error).toHaveBeenCalledWith(
        'Error getting current timezone:',
        expect.any(Error)
      );
    });

    it('should log a warning and fall back to UTC for invalid timezones', () => {
      const invalidTimezone = 'Invalid/Timezone';
      const result = handleInvalidTimezone(invalidTimezone);
      
      expect(result).toBe('UTC');
      expect(console.warn).toHaveBeenCalledWith(
        `System timezone ${invalidTimezone} is not valid, falling back to UTC`
      );
    });

    it('should call handleInvalidTimezone for invalid system timezone', () => {
      // Create a function that simulates getCurrentTimezone with an invalid timezone
      const simulateGetCurrentTimezoneWithInvalidTimezone = () => {
        try {
          const timezone = 'Invalid/Timezone';
          if (isValidTimezone(timezone)) {
            return timezone;
          } else {
            return handleInvalidTimezone(timezone);
          }
        } catch (error) {
          console.error("Error getting current timezone:", error);
          return "UTC";
        }
      };

      const timezone = simulateGetCurrentTimezoneWithInvalidTimezone();
      expect(timezone).toBe('UTC');
      expect(console.warn).toHaveBeenCalledWith(
        'System timezone Invalid/Timezone is not valid, falling back to UTC'
      );
    });
  });

  describe('getCurrentTimeInTimezone', () => {
    beforeEach(() => {
      console.error = jest.fn();
    });

    it('should format the current time in UTC', () => {
      // Mock the DateTimeFormat constructor with our helper
      mockDateTimeFormat(createMockFormatter());
      
      const time = getCurrentTimeInTimezone('UTC');
      
      // Check that it returns a string
      expect(typeof time).toBe('string');
      
      // Check that it's not the error message
      expect(time).not.toBe('Invalid timezone');
    });

    it('should format the current time in a specific timezone', () => {
      // Mock the DateTimeFormat constructor with our helper
      mockDateTimeFormat(createMockFormatter({
        formattedDate: '2023-01-01T12:00:00.000+01:00',
        timeZone: 'Europe/London',
        timeZoneValue: 'GMT+01:00'
      }));
      
      const time = getCurrentTimeInTimezone('Europe/London');
      
      // Check that it returns a string
      expect(typeof time).toBe('string');
      
      // Check that it's not the error message
      expect(time).not.toBe('Invalid timezone');
    });

    it('should return an error message for invalid timezones', () => {
      const time = getCurrentTimeInTimezone('Invalid/Timezone');
      expect(time).toBe('Invalid timezone');
    });

    it('should handle edge cases in date formatting', () => {
      // Mock DateTimeFormat to throw an error
      Intl.DateTimeFormat = jest.fn().mockImplementation(() => {
        throw new Error('Mock error');
      }) as any;
      
      // This should trigger the catch block in getCurrentTimeInTimezone
      const result = getCurrentTimeInTimezone('UTC');
      
      // Verify we got the error message
      expect(result).toBe('Invalid timezone');
      
      // Verify console.error was called
      expect(console.error).toHaveBeenCalled();
    });

    it('should handle empty timezone parts in formatting', () => {
      // First mock for the formatter that gets the timezone offset
      const mockEmptyFormatter = {
        format: jest.fn().mockReturnValue('2023-01-01') // No timezone part
      };
      
      // Second mock for the formatter that gets the date parts
      const mockTzFormatter = {
        formatToParts: jest.fn().mockReturnValue([
          { type: 'year', value: '2023' },
          { type: 'month', value: '01' },
          { type: 'day', value: '01' },
          { type: 'hour', value: '12' },
          { type: 'minute', value: '00' },
          { type: 'second', value: '00' }
          // No fractionalSecond to test that case
        ])
      };
      
      // Mock Date.toLocaleString to return a string without timezone
      Date.prototype.toLocaleString = jest.fn().mockReturnValue('January 1, 2023') as any;
      
      // Mock DateTimeFormat to return our formatters
      Intl.DateTimeFormat = jest.fn()
        .mockImplementationOnce(() => mockEmptyFormatter)
        .mockImplementationOnce(() => mockTzFormatter) as any;
      
      const result = getCurrentTimeInTimezone('UTC');
      
      // Verify the result contains the expected date format
      expect(result).toContain('2023-01-01T12:00:00.000');
    });

    it('should handle null values in split operations', () => {
      // Mock formatter with null format result
      const mockNullFormatter = {
        format: jest.fn().mockReturnValue(null)
      };
      
      // Mock DateTimeFormat to return our formatter
      Intl.DateTimeFormat = jest.fn().mockImplementation(() => mockNullFormatter) as any;
      
      const result = getCurrentTimeInTimezone('UTC');
      
      // The function should handle the null values and return an error
      expect(result).toBe('Invalid timezone');
      
      // Verify console.error was called
      expect(console.error).toHaveBeenCalled();
    });

    it('should handle empty result from formatter.format()', () => {
      // Mock for the formatter that returns empty string
      const mockEmptyFormatter = {
        format: jest.fn().mockReturnValue('')
      };
      
      // Mock for the formatter that gets the date parts
      const mockTzFormatter = {
        formatToParts: jest.fn().mockReturnValue([
          { type: 'year', value: '2023' },
          { type: 'month', value: '01' },
          { type: 'day', value: '01' },
          { type: 'hour', value: '12' },
          { type: 'minute', value: '00' },
          { type: 'second', value: '00' },
          { type: 'fractionalSecond', value: '123' }
        ])
      };
      
      // Mock Date.toLocaleString to return a valid string
      Date.prototype.toLocaleString = jest.fn().mockReturnValue('1/1/2023, 12:00:00 PM GMT+0000') as any;
      
      // Mock DateTimeFormat to return our formatters
      Intl.DateTimeFormat = jest.fn()
        .mockImplementationOnce(() => mockEmptyFormatter)
        .mockImplementationOnce(() => mockTzFormatter) as any;
      
      const result = getCurrentTimeInTimezone('UTC');
      
      // The function should handle the empty string and still return a result
      expect(result).toContain('2023-01-01T12:00:00.123');
    });

    it('should handle empty result from toLocaleString()', () => {
      // Mock for the formatter that returns valid string
      const mockFormatter = {
        format: jest.fn().mockReturnValue('1/1/2023, 12:00:00 PM GMT+0000')
      };
      
      // Mock for the formatter that gets the date parts
      const mockTzFormatter = {
        formatToParts: jest.fn().mockReturnValue([
          { type: 'year', value: '2023' },
          { type: 'month', value: '01' },
          { type: 'day', value: '01' },
          { type: 'hour', value: '12' },
          { type: 'minute', value: '00' },
          { type: 'second', value: '00' },
          { type: 'fractionalSecond', value: '123' }
        ])
      };
      
      // Mock Date.toLocaleString to return an empty string
      Date.prototype.toLocaleString = jest.fn().mockReturnValue('') as any;
      
      // Mock DateTimeFormat to return our formatters
      Intl.DateTimeFormat = jest.fn()
        .mockImplementationOnce(() => mockFormatter)
        .mockImplementationOnce(() => mockTzFormatter) as any;
      
      const result = getCurrentTimeInTimezone('UTC');
      
      // The function should handle the empty string and still return a result
      expect(result).toContain('2023-01-01T12:00:00.123');
    });
  });

  describe('handleInvalidTimezone', () => {
    beforeEach(() => {
      console.warn = jest.fn();
    });

    it('should log a warning and return UTC', () => {
      const invalidTimezone = 'Invalid/Timezone';
      const result = handleInvalidTimezone(invalidTimezone);
      
      expect(result).toBe('UTC');
      expect(console.warn).toHaveBeenCalledWith(
        `System timezone ${invalidTimezone} is not valid, falling back to UTC`
      );
    });

    it('should be called when isValidTimezone returns false', () => {
      // Create a test function that simulates the exact code path
      const testInvalidTimezone = (timezone: string) => {
        if (isValidTimezone(timezone)) {
          return timezone;
        }
        return handleInvalidTimezone(timezone);
      };

      // Use a timezone that we know is invalid
      const result = testInvalidTimezone('Invalid/Timezone');
      
      expect(result).toBe('UTC');
      expect(console.warn).toHaveBeenCalledWith(
        'System timezone Invalid/Timezone is not valid, falling back to UTC'
      );
    });
  });

  describe('processTimezone', () => {
    beforeEach(() => {
      console.warn = jest.fn();
    });

    it('should return the timezone if it is valid', () => {
      const validTimezone = 'UTC';
      const result = processTimezone(validTimezone);
      expect(result).toBe(validTimezone);
      expect(console.warn).not.toHaveBeenCalled();
    });

    it('should call handleInvalidTimezone for invalid timezones', () => {
      const invalidTimezone = 'Invalid/Timezone';
      const result = processTimezone(invalidTimezone);
      expect(result).toBe('UTC');
      expect(console.warn).toHaveBeenCalledWith(
        `System timezone ${invalidTimezone} is not valid, falling back to UTC`
      );
    });
  });
});
```