menios icon indicating copy to clipboard operation
menios copied to clipboard

Basic timezone support (TZ environment variable)

Open pbalduino opened this issue 3 months ago • 0 comments

Goal

Implement basic timezone support through TZ environment variable parsing.

Context

Kernel/libc ignore timezone offsets. Issue #299 tracks IANA database work, but #226 needs at least UTC offset/TZ environment plumbing for localtime().

Current State

  • ✅ gmtime_r() works (always UTC)
  • ❌ localtime() ignores TZ, returns same as gmtime()
  • ❌ No TZ environment variable parsing
  • ❌ No UTC offset calculation

What's Needed

1. TZ Environment Variable Parsing

Support basic TZ formats:

// Parse TZ environment variable
// Examples:
//   TZ="UTC"           -> offset 0
//   TZ="GMT"           -> offset 0
//   TZ="EST5"          -> offset -5 hours
//   TZ="PST8PDT"       -> offset -8 hours (no DST for now)
//   TZ="CET-1CEST"     -> offset +1 hour
//   TZ="UTC-3"         -> offset +3 hours
//   TZ="UTC+5"         -> offset -5 hours (note: sign flip!)

struct timezone_info {
    char name[8];      // Timezone name (e.g., "PST")
    int offset_sec;    // Offset from UTC in seconds
    bool has_dst;      // Has daylight saving time?
    char dst_name[8];  // DST name (e.g., "PDT")
    int dst_offset;    // DST offset (usually +1 hour)
};

int parse_tz_string(const char *tz, struct timezone_info *info);

2. localtime() Implementation

struct tm *localtime(const time_t *timep) {
    static struct tm tm_buf;
    return localtime_r(timep, &tm_buf);
}

struct tm *localtime_r(const time_t *timep, struct tm *result) {
    const char *tz = getenv("TZ");
    time_t local_time = *timep;
    
    if (tz && *tz) {
        struct timezone_info tz_info;
        if (parse_tz_string(tz, &tz_info) == 0) {
            // Apply timezone offset
            local_time += tz_info.offset_sec;
        }
    }
    
    // Convert adjusted time to struct tm
    return gmtime_r(&local_time, result);
}

3. mktime() with Timezone

time_t mktime(struct tm *tm) {
    // Assume tm is in local time
    const char *tz = getenv("TZ");
    time_t utc_time;
    
    // Convert to UTC first
    utc_time = tm_to_unix_time(tm);
    
    // Adjust for timezone
    if (tz && *tz) {
        struct timezone_info tz_info;
        if (parse_tz_string(tz, &tz_info) == 0) {
            // Subtract offset to get UTC
            utc_time -= tz_info.offset_sec;
        }
    }
    
    return utc_time;
}

4. strftime() with Timezone

Update strftime() to respect %Z (timezone name) and %z (offset):

case 'Z':
    // Timezone name
    tz = getenv("TZ");
    if (tz && *tz) {
        struct timezone_info tz_info;
        if (parse_tz_string(tz, &tz_info) == 0) {
            len = snprintf(p, remaining, "%s", tz_info.name);
        } else {
            len = snprintf(p, remaining, "UTC");
        }
    }
    break;

case 'z':
    // UTC offset (+0530, -0800)
    tz = getenv("TZ");
    if (tz && *tz) {
        struct timezone_info tz_info;
        if (parse_tz_string(tz, &tz_info) == 0) {
            int hours = tz_info.offset_sec / 3600;
            int mins = (abs(tz_info.offset_sec) / 60) % 60;
            len = snprintf(p, remaining, "%+03d%02d", hours, mins);
        }
    }
    break;

Implementation Strategy

Phase 1: TZ Parsing

  1. Implement parse_tz_string() for basic formats
  2. Support named timezones (EST, PST, CET, etc.)
  3. Support UTC offset syntax (UTC+5, UTC-3)
  4. Support POSIX TZ format (EST5EDT)
  5. Unit tests for TZ parsing

Phase 2: localtime() Support

  1. Update localtime_r() to use TZ
  2. Implement localtime() wrapper
  3. Test with various TZ values
  4. Verify UTC stays unchanged

Phase 3: mktime() Support

  1. Update mktime() to apply TZ offset
  2. Test round-trip: localtime -> mktime -> localtime
  3. Verify UTC conversion

Phase 4: strftime() Integration

  1. Update %Z to show timezone name
  2. Update %z to show UTC offset
  3. Test formatted output

Files to Modify

  • libc/src/time/localtime.c (new)
  • libc/src/time/mktime.c - Update for TZ
  • libc/src/time/strftime.c - Add %Z and %z support
  • libc/src/time/tz_parse.c (new) - TZ parsing
  • include/time.h - Add localtime() declaration
  • test/test_timezone.c (new)

Usage Examples

Set timezone

#include <stdlib.h>
#include <time.h>

// Set timezone to Pacific Standard Time
setenv("TZ", "PST8", 1);
tzset();  // Optional: notify libc of TZ change

time_t now = time(NULL);
struct tm *local = localtime(&now);
printf("Local time: %s", asctime(local));

Format with timezone

#include <time.h>

setenv("TZ", "EST5", 1);

time_t now = time(NULL);
struct tm *local = localtime(&now);

char buf[64];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %Z (%z)", local);
printf("%s\n", buf);
// Output: 2025-10-20 14:30:00 EST (-0500)

Convert between timezones

#include <time.h>

time_t now = time(NULL);

// UTC time
struct tm *utc = gmtime(&now);
printf("UTC: %s", asctime(utc));

// PST time
setenv("TZ", "PST8", 1);
struct tm *pst = localtime(&now);
printf("PST: %s", asctime(pst));

// EST time
setenv("TZ", "EST5", 1);
struct tm *est = localtime(&now);
printf("EST: %s", asctime(est));

Testing

  • Parse various TZ formats correctly
  • localtime() applies correct offset
  • mktime() reverses localtime()
  • strftime() shows correct timezone
  • UTC (gmtime) unaffected by TZ
  • Empty/invalid TZ defaults to UTC
  • Edge cases: midnight, DST boundaries

Limitations (Initial Implementation)

  • No DST transitions: DST rules ignored, only standard time
  • No historical data: Current offset only, no past changes
  • No IANA database: Full zoneinfo files in #299
  • No automatic DST: User must manually change TZ

Dependencies

  • #290 - Time conversions (gmtime/mktime/strftime) (✅ complete)
  • #307 - Environment variable access (✅ complete)

Future Work (Issue #299)

  • Full IANA timezone database
  • DST transition rules
  • Historical timezone changes
  • Automatic DST handling
  • zoneinfo file parsing

Parent Issue

  • #226 - RTC and time management

Related Issue

  • #299 - Timezone support (IANA database) - full implementation

Standards Compliance

  • POSIX TZ environment variable format
  • Basic timezone offset support
  • Compatible with C standard library localtime()

pbalduino avatar Oct 20 '25 15:10 pbalduino