Skip to main content
Glama
IBM

chuk-mcp-time

by IBM

get_timezone_info

Retrieve timezone metadata including current offset, DST status, and upcoming transitions to understand timezone behavior and plan accordingly.

Instructions

Get detailed information about a timezone including upcoming transitions.

Provides comprehensive timezone metadata including current offset, DST status,
and upcoming transitions (e.g., DST changes). Useful for planning and
understanding timezone behavior.

Args:
    timezone: IANA timezone identifier
    mode: Accuracy mode for getting current time - "fast" or "accurate"

Returns:
    TimezoneDetailResponse with current info and transition schedule

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
timezoneYes
modeNofast

Implementation Reference

  • The main async handler for the 'get_timezone_info' MCP tool. It gets the current UTC time via consensus, then retrieves current timezone offset info and upcoming transitions (DST changes) using helper functions from timezone_utils.
    @tool  # type: ignore[arg-type]
    async def get_timezone_info(
        timezone: str,
        mode: AccuracyMode = AccuracyMode.FAST,
    ) -> TimezoneDetailResponse:
        """Get detailed information about a timezone including upcoming transitions.
    
        Provides comprehensive timezone metadata including current offset, DST status,
        and upcoming transitions (e.g., DST changes). Useful for planning and
        understanding timezone behavior.
    
        Args:
            timezone: IANA timezone identifier
            mode: Accuracy mode for getting current time - "fast" or "accurate"
    
        Returns:
            TimezoneDetailResponse with current info and transition schedule
        """
        # Get accurate current time
        time_response = await get_time_utc(mode=mode)  # type: ignore[misc]
        utc_now = datetime.fromtimestamp(time_response.epoch_ms / 1000.0, tz=UTC)
    
        # Get current timezone info
        current_info = get_timezone_info_at_datetime(timezone, utc_now)
    
        # Find transitions in next 2 years
        end_time = datetime.fromtimestamp(
            (time_response.epoch_ms / 1000.0) + (365 * 2 * 24 * 3600), tz=UTC
        )
        transitions_data = find_timezone_transitions(timezone, utc_now, end_time)
    
        transitions = [
            TimezoneTransition(
                from_datetime=t.from_datetime.isoformat(),
                utc_offset_seconds=t.utc_offset_seconds,
                is_dst=t.is_dst,
                abbreviation=t.abbreviation,
            )
            for t in transitions_data
        ]
    
        return TimezoneDetailResponse(
            timezone=timezone,
            country_code=None,  # Would need full zone1970.tab parsing
            comment=None,
            current_offset_seconds=current_info.utc_offset_seconds,
            current_is_dst=current_info.is_dst,
            current_abbreviation=current_info.abbreviation,
            transitions=transitions,
            tzdata_version=get_tzdata_version(),
        )
  • The core helper function `get_timezone_info_at_datetime` that computes UTC offset, DST status, and abbreviation for a given IANA timezone at a specific datetime using ZoneInfo.
    def get_timezone_info_at_datetime(tz_name: str, dt: datetime) -> TimezoneOffsetInfo:
        """Get timezone information at a specific datetime.
    
        Args:
            tz_name: IANA timezone identifier
            dt: Datetime to query (should be timezone-aware)
    
        Returns:
            TimezoneOffsetInfo with offset, DST status, and abbreviation
        """
        tz = ZoneInfo(tz_name)
        local_dt = dt.astimezone(tz)
    
        # Get offset in seconds
        offset = local_dt.utcoffset()
        offset_seconds = int(offset.total_seconds()) if offset else 0
    
        # Check if DST is active
        dst = local_dt.dst()
        is_dst = bool(dst and dst.total_seconds() != 0)
    
        # Get abbreviation
        abbreviation = local_dt.tzname() or ""
    
        return TimezoneOffsetInfo(
            utc_offset_seconds=offset_seconds,
            is_dst=is_dst,
            abbreviation=abbreviation,
        )
  • The `TimezoneDetailResponse` Pydantic model — the response schema for the get_timezone_info tool, containing timezone, current offset, DST flag, abbreviation, transitions list, and tzdata version.
    class TimezoneDetailResponse(BaseModel):
        """Response for get_timezone_info tool."""
    
        timezone: str = Field(description="IANA timezone identifier")
        country_code: str | None = Field(description="ISO 3166 country code")
        comment: str | None = Field(description="Additional timezone information")
        current_offset_seconds: int = Field(description="Current UTC offset in seconds")
        current_is_dst: bool = Field(description="Whether DST is currently active")
        current_abbreviation: str = Field(description="Current timezone abbreviation")
        transitions: list[TimezoneTransition] = Field(description="Upcoming timezone transitions")
        tzdata_version: str = Field(description="IANA tzdata version")
  • The public API registration: `get_timezone_info` is re-exported from the package's __init__.py and listed in __all__ so it is discoverable.
    """High-accuracy time oracle MCP server using NTP consensus."""
    
    __version__ = "1.0.0"
    
    from chuk_mcp_time.server import (
        compare_system_clock,
        convert_time,
        get_local_time,
        get_time_for_timezone,
        get_time_utc,
        get_timezone_info,
        list_timezones,
        main,
    )
    
    __all__ = [
        "get_time_utc",
        "get_time_for_timezone",
        "get_local_time",
        "compare_system_clock",
        "convert_time",
        "list_timezones",
        "get_timezone_info",
        "main",
        "__version__",
    ]
  • The `@tool` decorator on the handler function registers it as an MCP tool in the server.
        get_tzdata_version,
        list_all_timezones,
    )
    
    # Initialize components
    _config = get_config()
    _ntp_client = NTPClient(timeout=_config.ntp_timeout)
    _consensus_engine = TimeConsensusEngine(
        max_outlier_deviation_ms=_config.max_outlier_deviation_ms,
        min_sources=_config.min_sources,
        max_disagreement_ms=_config.max_disagreement_ms,
    )
    
    
    @tool  # type: ignore[arg-type]
    async def get_time_utc(
        mode: AccuracyMode = AccuracyMode.FAST,
        compensate_latency: bool = True,
    ) -> TimeResponse:
        """Get current UTC time with high accuracy using NTP consensus.
    
        Queries multiple NTP servers, removes outliers, and computes a consensus time
        that is independent of the system clock. Returns detailed information about
        all sources, consensus method, and estimated error.
    
        By default, the returned timestamp is compensated for the time it took to
        query NTP servers and compute consensus. This means the timestamp represents
        the time when the response is returned, not when NTP servers were queried.
    
        Args:
            mode: Accuracy mode - "fast" uses 3-4 servers, "accurate" uses 7 servers
            compensate_latency: If True, add query duration to timestamp (default: True)
    
        Returns:
            TimeResponse with consensus time and metadata
        """
        # Record start time for latency compensation
        query_start = time_module.time()
    
        # Select servers based on mode
        if mode == AccuracyMode.FAST:
            servers = _config.ntp_servers[: _config.fast_mode_server_count]
        else:
            servers = _config.ntp_servers
    
        # Query NTP servers asynchronously
        responses = await _ntp_client.query_multiple_servers(servers)
    
        # Compute consensus
        consensus = _consensus_engine.compute_consensus(responses)
    
        # Calculate query duration
        query_duration = time_module.time() - query_start
        query_duration_ms = query_duration * 1000
    
        # Apply latency compensation if requested
        if compensate_latency:
            # Add query duration to consensus timestamp
            compensated_timestamp = consensus.timestamp + query_duration
            compensated_epoch_ms = int(compensated_timestamp * 1000)
            compensated_iso8601 = datetime.fromtimestamp(compensated_timestamp, tz=UTC).isoformat()
    
            # Add note to warnings if compensation is significant
            warnings = list(consensus.warnings)
            if query_duration_ms > 100:
                warnings.append(f"Applied +{query_duration_ms:.1f}ms latency compensation to timestamp")
    
            # Update estimated error to include query duration uncertainty
            # The longer the query took, the more uncertainty we add
            adjusted_error = consensus.estimated_error_ms + (query_duration_ms * 0.1)
    
            # Recalculate system delta with compensated timestamp
            # System delta should reflect the compensated timestamp, not the original
            current_system_time = time_module.time()
            system_delta_ms = (current_system_time - compensated_timestamp) * 1000
    
            iso8601_time = compensated_iso8601
            epoch_ms = compensated_epoch_ms
            estimated_error_ms = adjusted_error
        else:
            iso8601_time = consensus.iso8601_time
            epoch_ms = consensus.epoch_ms
            estimated_error_ms = consensus.estimated_error_ms
            warnings = consensus.warnings
            system_delta_ms = consensus.system_delta_ms
    
        # Convert source samples to dict for JSON serialization
        source_samples = [s.model_dump() for s in consensus.source_samples]
    
        return TimeResponse(
            iso8601_time=iso8601_time,
            epoch_ms=epoch_ms,
            sources_used=consensus.sources_used,
            total_sources=consensus.total_sources,
            consensus_method=consensus.consensus_method.value,
            estimated_error_ms=estimated_error_ms,
            source_samples=source_samples,
            warnings=warnings,
            system_time=consensus.system_time,
            system_delta_ms=system_delta_ms,
            query_duration_ms=query_duration_ms,
            latency_compensated=compensate_latency,
        )
    
    
    @tool  # type: ignore[arg-type]
    async def get_time_for_timezone(
        timezone_name: str,
        mode: AccuracyMode = AccuracyMode.FAST,
        compensate_latency: bool = True,
    ) -> TimezoneResponse:
        """Get current time for a specific timezone with high accuracy.
    
        Queries multiple NTP servers for accurate UTC time, then converts to the
        requested timezone. Includes all consensus metadata and source details.
    
        Args:
            timezone_name: IANA timezone name (e.g., "America/New_York")
            mode: Accuracy mode - "fast" or "accurate"
            compensate_latency: If True, add query duration to timestamp (default: True)
    
        Returns:
            TimezoneResponse with time in specified timezone
        """
        # Get UTC consensus first
        time_response = await get_time_utc(mode=mode, compensate_latency=compensate_latency)  # type: ignore[misc]
    
        # Convert to requested timezone
        try:
            from zoneinfo import ZoneInfo
    
            utc_timestamp = time_response.epoch_ms / 1000.0
            utc_dt = datetime.fromtimestamp(utc_timestamp, tz=UTC)
            local_dt = utc_dt.astimezone(ZoneInfo(timezone_name))
            local_time = local_dt.isoformat()
    
            return TimezoneResponse(
                **time_response.model_dump(),
                timezone=timezone_name,
                local_time=local_time,
            )
    
        except Exception as e:
            # If timezone conversion fails, add error to warnings
            warnings = list(time_response.warnings)
            warnings.append(f"Failed to convert to timezone {timezone_name}: {e}")
    
            # Get base data but exclude warnings since we're overriding it
            base_data = time_response.model_dump(exclude={"warnings"})
    
            return TimezoneResponse(
                **base_data,
                timezone=timezone_name,
                local_time=f"ERROR: {e}",
                warnings=warnings,
            )
    
    
    @tool  # type: ignore[arg-type]
    async def compare_system_clock(
        mode: AccuracyMode = AccuracyMode.FAST,
    ) -> ClockComparisonResponse:
        """Compare system clock against trusted NTP time sources.
    
        Useful for detecting system clock drift or misconfiguration. Queries NTP
        servers and reports the difference between system time and consensus time.
    
        Args:
            mode: Accuracy mode - "fast" or "accurate"
    
        Returns:
            ClockComparisonResponse with comparison data
        """
        time_response = await get_time_utc(mode=mode)  # type: ignore[misc]
    
        # Determine status based on delta
        abs_delta = abs(time_response.system_delta_ms)
        if abs_delta < 100:
            status = ClockStatus.OK
        elif abs_delta < 1000:
            status = ClockStatus.DRIFT
        else:
            status = ClockStatus.ERROR
    
        return ClockComparisonResponse(
            system_time=time_response.system_time,
            trusted_time=time_response.iso8601_time,
            delta_ms=time_response.system_delta_ms,
            estimated_error_ms=time_response.estimated_error_ms,
            status=status,
        )
    
    
    @tool  # type: ignore[arg-type]
    async def get_local_time(
        timezone: str,
        mode: AccuracyMode = AccuracyMode.FAST,
        compensate_latency: bool = True,
    ) -> LocalTimeResponse:
        """Get current time for a specific IANA timezone with high accuracy.
    
        Uses NTP consensus for accurate UTC time, then converts to the requested
        timezone using IANA tzdata. This provides authoritative local time independent
        of system clock accuracy.
    
        Args:
            timezone: IANA timezone identifier (e.g., "America/New_York", "Europe/London")
            mode: Accuracy mode - "fast" or "accurate"
            compensate_latency: If True, add query duration to timestamp (default: True)
    
        Returns:
            LocalTimeResponse with local time and timezone metadata
        """
        # Get accurate UTC time
        time_response = await get_time_utc(mode=mode, compensate_latency=compensate_latency)  # type: ignore[misc]
    
        # Convert to local timezone
        utc_timestamp = time_response.epoch_ms / 1000.0
        utc_dt = datetime.fromtimestamp(utc_timestamp, tz=UTC)
    
        # Get timezone info
        tz_info = get_timezone_info_at_datetime(timezone, utc_dt)
    
        # Apply timezone
        from zoneinfo import ZoneInfo
    
        local_dt = utc_dt.astimezone(ZoneInfo(timezone))
    
        return LocalTimeResponse(
            local_datetime=local_dt.isoformat(),
            timezone=timezone,
            utc_offset_seconds=tz_info.utc_offset_seconds,
            is_dst=tz_info.is_dst,
            abbreviation=tz_info.abbreviation,
            source_utc=time_response.iso8601_time,
            tzdata_version=get_tzdata_version(),
            estimated_error_ms=time_response.estimated_error_ms,
        )
    
    
    @tool  # type: ignore[arg-type]
    async def convert_time(
        datetime_str: str,
        from_timezone: str,
        to_timezone: str,
    ) -> TimezoneConversionResponse:
        """Convert a datetime from one timezone to another using IANA rules.
    
        Performs timezone conversion independent of system clock. Uses IANA tzdata
        to handle all DST transitions, historical changes, and political boundaries.
    
        Args:
            datetime_str: ISO 8601 datetime string (naive, will be interpreted in from_timezone)
            from_timezone: Source IANA timezone identifier
            to_timezone: Target IANA timezone identifier
    
        Returns:
            TimezoneConversionResponse with conversion details and explanation
        """
        result = convert_datetime_between_timezones(datetime_str, from_timezone, to_timezone)
    
        return TimezoneConversionResponse(
            from_timezone=result.from_timezone,
            from_datetime=result.from_datetime.isoformat(),
            from_utc_offset_seconds=result.from_utc_offset_seconds,
            to_timezone=result.to_timezone,
            to_datetime=result.to_datetime.isoformat(),
            to_utc_offset_seconds=result.to_utc_offset_seconds,
            offset_difference_seconds=result.offset_difference_seconds,
            explanation=result.explanation,
        )
    
    
    @tool  # type: ignore[arg-type]
    async def list_timezones(
        country_code: str | None = None,
        search: str | None = None,
    ) -> ListTimezonesResponse:
        """List available IANA timezones with optional filtering.
    
        Returns all valid IANA timezone identifiers. Helps discover correct timezone
        names and prevents hallucination of invalid timezones.
    
        Args:
            country_code: Optional ISO 3166 country code filter (e.g., "US", "GB", "FR")
            search: Optional substring search filter (case-insensitive)
    
        Returns:
            ListTimezonesResponse with list of timezones and metadata
        """
        timezones_data = list_all_timezones(country_code=country_code, search=search)
    
        timezones = [
            TimezoneInfo(
                id=tz.id,
                country_code=tz.country_code,
                comment=tz.comment,
                example_city=tz.example_city,
            )
            for tz in timezones_data
        ]
    
        return ListTimezonesResponse(
            timezones=timezones,
            total_count=len(timezones),
            tzdata_version=get_tzdata_version(),
        )
    
    
    @tool  # type: ignore[arg-type]
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description fully discloses the tool's behavior: it returns current offset, DST status, and upcoming transitions. It describes the mode parameter's effect on accuracy. However, it does not mention error handling or if the tool is read-only.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with an overview, args section, and returns. Every sentence adds value without redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no annotations or output schema, the description covers the tool's purpose, parameters, and return type adequately. It could mention that the tool is safe/read-only, but overall it is complete for its complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, but the description explains both parameters: timezone as 'IANA timezone identifier' and mode as 'Accuracy mode ... fast or accurate', adding meaningful context beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it provides detailed timezone information including upcoming transitions, distinguishing it from siblings like 'get_time_for_timezone' or 'list_timezones'.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description mentions it is 'useful for planning and understanding timezone behavior' but does not explicitly state when to use this tool vs alternatives or when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/IBM/chuk-mcp-time'

If you have feedback or need assistance with the MCP directory API, please join our Discord server