menios
menios copied to clipboard
Basic timezone support (TZ environment variable)
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
- Implement parse_tz_string() for basic formats
- Support named timezones (EST, PST, CET, etc.)
- Support UTC offset syntax (UTC+5, UTC-3)
- Support POSIX TZ format (EST5EDT)
- Unit tests for TZ parsing
Phase 2: localtime() Support
- Update localtime_r() to use TZ
- Implement localtime() wrapper
- Test with various TZ values
- Verify UTC stays unchanged
Phase 3: mktime() Support
- Update mktime() to apply TZ offset
- Test round-trip: localtime -> mktime -> localtime
- Verify UTC conversion
Phase 4: strftime() Integration
- Update %Z to show timezone name
- Update %z to show UTC offset
- 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()