diff --git a/docs/README.skills.md b/docs/README.skills.md index 092d8703..81023303 100644 --- a/docs/README.skills.md +++ b/docs/README.skills.md @@ -110,6 +110,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to | [documentation-writer](../skills/documentation-writer/SKILL.md) | Diátaxis Documentation Expert. An expert technical writer specializing in creating high-quality software documentation, guided by the principles and structure of the Diátaxis technical documentation authoring framework. | None | | [dotnet-best-practices](../skills/dotnet-best-practices/SKILL.md) | Ensure .NET/C# code meets best practices for the solution/project. | None | | [dotnet-design-pattern-review](../skills/dotnet-design-pattern-review/SKILL.md) | Review the C#/.NET code for design pattern implementation and suggest improvements. | None | +| [dotnet-timezone](../skills/dotnet-timezone/SKILL.md) | .NET timezone handling guidance for C# applications. Use when working with TimeZoneInfo, DateTimeOffset, NodaTime, UTC conversion, daylight saving time, scheduling across timezones, cross-platform Windows/IANA timezone IDs, or when a .NET user needs the timezone for a city, address, region, or country and copy-paste-ready C# code. | `references/code-patterns.md`
`references/timezone-index.md` | | [dotnet-upgrade](../skills/dotnet-upgrade/SKILL.md) | Ready-to-use prompts for comprehensive .NET framework upgrade analysis and execution | None | | [doublecheck](../skills/doublecheck/SKILL.md) | Three-layer verification pipeline for AI output. Extracts verifiable claims, finds supporting or contradicting sources via web search, runs adversarial review for hallucination patterns, and produces a structured verification report with source links for human review. | `assets/verification-report-template.md` | | [editorconfig](../skills/editorconfig/SKILL.md) | Generates a comprehensive and best-practice-oriented .editorconfig file based on project analysis and user preferences. | None | diff --git a/skills/dotnet-timezone/SKILL.md b/skills/dotnet-timezone/SKILL.md new file mode 100644 index 00000000..94dc2a9c --- /dev/null +++ b/skills/dotnet-timezone/SKILL.md @@ -0,0 +1,109 @@ +--- +name: dotnet-timezone +description: '.NET timezone handling guidance for C# applications. Use when working with TimeZoneInfo, DateTimeOffset, NodaTime, UTC conversion, daylight saving time, scheduling across timezones, cross-platform Windows/IANA timezone IDs, or when a .NET user needs the timezone for a city, address, region, or country and copy-paste-ready C# code.' +--- + +# .NET Timezone + +Resolve timezone questions for .NET and C# code with production-safe guidance and copy-paste-ready snippets. + +## Start With The Right Path + +Identify the request type first: + +- Address or location lookup +- Timezone ID lookup +- UTC/local conversion +- Cross-platform timezone compatibility +- Scheduling or DST handling +- API or persistence design + +If the library is unclear, default to `TimeZoneConverter` for cross-platform work. If the scenario involves recurring schedules or strict DST rules, prefer `NodaTime`. + +## Resolve Addresses And Locations + +If the user provides an address, city, region, country, or document containing place names: + +1. Extract each location from the input. +2. Read `references/timezone-index.md` for common Windows and IANA mappings. +3. If the exact location is not listed, infer the correct IANA zone from geography, then map it to the Windows ID. +4. Return both IDs and a ready-to-use C# example. + +For each resolved location, provide: + +```text +Location: +Windows ID: +IANA ID: +UTC offset: +DST: +``` + +Then include a cross-platform snippet like: + +```csharp +using TimeZoneConverter; + +TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Asia/Colombo"); +DateTime local = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz); +``` + +If multiple locations are present, include one block per location and then a combined multi-timezone snippet. + +If a location is ambiguous, list the possible timezone matches and ask the user to choose the correct one. + +## Look Up Timezone IDs + +Use `references/timezone-index.md` for Windows to IANA mappings. + +Always provide both formats: + +- Windows ID for `TimeZoneInfo.FindSystemTimeZoneById()` on Windows +- IANA ID for Linux, containers, `NodaTime`, and `TimeZoneConverter` + +## Generate Code + +Use `references/code-patterns.md` and pick the smallest pattern that fits: + +- Pattern 1: `TimeZoneInfo` for Windows-only code +- Pattern 2: `TimeZoneConverter` for cross-platform conversion +- Pattern 3: `NodaTime` for strict timezone arithmetic and DST-sensitive scheduling +- Pattern 4: `DateTimeOffset` for APIs and data transfer +- Pattern 5: ASP.NET Core persistence and presentation +- Pattern 6: recurring jobs and schedulers +- Pattern 7: ambiguous and invalid DST timestamps + +Always include package guidance when recommending third-party libraries. + +## Warn About Common Pitfalls + +Mention the relevant warning when applicable: + +- `TimeZoneInfo.FindSystemTimeZoneById()` is platform-specific for timezone IDs. +- Avoid storing `DateTime.Now` in databases; store UTC instead. +- Treat `DateTimeKind.Unspecified` as a bug risk unless it is deliberate input. +- DST transitions can skip or repeat local times. +- Azure Windows and Azure Linux environments may expect different timezone ID formats. + +## Response Shape + +For address and location requests: + +1. Return the resolved timezone block for each location. +2. State the recommended implementation in one sentence. +3. Include a copy-paste-ready C# snippet. + +For code and architecture requests: + +1. State the recommended approach in one sentence. +2. Provide the timezone IDs if relevant. +3. Include the minimal working code snippet. +4. Mention the package requirement if needed. +5. Add one pitfall warning if it matters. + +Keep responses concise and code-first. + +## References + +- `references/timezone-index.md`: common Windows and IANA timezone mappings +- `references/code-patterns.md`: ready-to-use .NET timezone patterns diff --git a/skills/dotnet-timezone/references/code-patterns.md b/skills/dotnet-timezone/references/code-patterns.md new file mode 100644 index 00000000..eb930050 --- /dev/null +++ b/skills/dotnet-timezone/references/code-patterns.md @@ -0,0 +1,153 @@ +# .NET Timezone Code Patterns + +## Pattern 1: Basic TimeZoneInfo + +Use this only when the application is Windows-only and Windows timezone IDs are acceptable. + +```csharp +DateTime utcNow = DateTime.UtcNow; +TimeZoneInfo sriLankaTz = TimeZoneInfo.FindSystemTimeZoneById("Sri Lanka Standard Time"); +DateTime localTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, sriLankaTz); + +DateTime backToUtc = TimeZoneInfo.ConvertTimeToUtc(localTime, sriLankaTz); + +TimeZoneInfo tokyoTz = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time"); +DateTime tokyoTime = TimeZoneInfo.ConvertTime(localTime, sriLankaTz, tokyoTz); +``` + +Use `TimeZoneConverter` or `NodaTime` instead for Linux, containers, or mixed environments. + +## Pattern 2: Cross-Platform With TimeZoneConverter + +Recommended default for most .NET apps that run across Windows and Linux. + +```xml + +``` + +```csharp +using TimeZoneConverter; + +TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Asia/Colombo"); +DateTime converted = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz); +``` + +This also accepts Windows IDs: + +```csharp +TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Sri Lanka Standard Time"); +``` + +## Pattern 3: NodaTime + +Use this for strict timezone arithmetic, recurring schedules, or DST edge cases where correctness matters more than minimal dependencies. + +```xml + +``` + +```csharp +using NodaTime; + +DateTimeZone colomboZone = DateTimeZoneProviders.Tzdb["Asia/Colombo"]; +Instant now = SystemClock.Instance.GetCurrentInstant(); +ZonedDateTime colomboTime = now.InZone(colomboZone); + +DateTimeZone tokyoZone = DateTimeZoneProviders.Tzdb["Asia/Tokyo"]; +ZonedDateTime tokyoTime = colomboTime.WithZone(tokyoZone); + +LocalDateTime localDt = new LocalDateTime(2024, 6, 15, 14, 30, 0); +ZonedDateTime zoned = colomboZone.AtStrictly(localDt); +Instant utcInstant = zoned.ToInstant(); +``` + +## Pattern 4: DateTimeOffset For APIs + +Prefer `DateTimeOffset` for values crossing service or process boundaries. + +```csharp +using TimeZoneConverter; + +DateTimeOffset utcNow = DateTimeOffset.UtcNow; +TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Asia/Colombo"); +DateTimeOffset colomboTime = TimeZoneInfo.ConvertTime(utcNow, tz); +``` + +## Pattern 5: ASP.NET Core Persistence And Presentation + +Store UTC, convert at the edges. + +```csharp +using TimeZoneConverter; + +entity.CreatedAtUtc = DateTime.UtcNow; + +public DateTimeOffset ToUserTime(DateTime utc, string userIanaTimezone) +{ + var tz = TZConvert.GetTimeZoneInfo(userIanaTimezone); + return TimeZoneInfo.ConvertTimeFromUtc(utc, tz); +} +``` + +## Pattern 6: Scheduling And Recurring Jobs + +Translate a user-facing local time to UTC before scheduling. + +```csharp +using TimeZoneConverter; + +TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Asia/Colombo"); +DateTime scheduledLocal = new DateTime(2024, 12, 1, 9, 0, 0, DateTimeKind.Unspecified); +DateTime scheduledUtc = TimeZoneInfo.ConvertTimeToUtc(scheduledLocal, tz); +``` + +With Hangfire: + +```csharp +RecurringJob.AddOrUpdate( + "morning-job", + () => DoWork(), + "0 9 * * *", + new RecurringJobOptions { TimeZone = tz }); +``` + +## Pattern 7: Ambiguous And Invalid DST Times + +Check for repeated or skipped local timestamps when the timezone observes daylight saving time. + +```csharp +using TimeZoneConverter; + +TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("America/New_York"); +DateTime localTime = new DateTime(2024, 11, 3, 1, 30, 0); + +if (tz.IsAmbiguousTime(localTime)) +{ + var offsets = tz.GetAmbiguousTimeOffsets(localTime); + var standardOffset = offsets.Min(); + var dto = new DateTimeOffset(localTime, standardOffset); +} + +if (tz.IsInvalidTime(localTime)) +{ + localTime = localTime.AddHours(1); +} +``` + +## Common Mistakes + +| Wrong | Better | +| --- | --- | +| `DateTime.Now` in server code | `DateTime.UtcNow` | +| Storing local timestamps in the database | Store UTC and convert for display | +| Hardcoding offsets such as `+05:30` | Use timezone IDs | +| Using `FindSystemTimeZoneById("Asia/Colombo")` on Windows | Use `TZConvert.GetTimeZoneInfo("Asia/Colombo")` | +| Comparing local `DateTime` values from different zones | Compare UTC or use `DateTimeOffset` | +| Creating `DateTime` without intentional kind semantics | Use `Utc`, `Local`, or deliberate `Unspecified` | + +## Decision Guide + +- Use `TimeZoneInfo` only for Windows-only code with Windows IDs. +- Use `TimeZoneConverter` for most cross-platform applications. +- Use `NodaTime` when DST arithmetic or calendaring accuracy is central. +- Use `DateTimeOffset` for APIs and serialized timestamps. diff --git a/skills/dotnet-timezone/references/timezone-index.md b/skills/dotnet-timezone/references/timezone-index.md new file mode 100644 index 00000000..fd588be2 --- /dev/null +++ b/skills/dotnet-timezone/references/timezone-index.md @@ -0,0 +1,87 @@ +# .NET Timezone Reference Index + +## Windows To IANA Mapping + +Use this file for common mappings between Windows timezone IDs and IANA timezone IDs. + +### Asia And Pacific + +| Display Name | Windows ID | IANA ID | UTC Offset | DST? | +| --- | --- | --- | --- | --- | +| Sri Lanka Standard Time | Sri Lanka Standard Time | Asia/Colombo | +05:30 | No | +| India Standard Time | India Standard Time | Asia/Calcutta | +05:30 | No | +| Pakistan Standard Time | Pakistan Standard Time | Asia/Karachi | +05:00 | No | +| Bangladesh Standard Time | Bangladesh Standard Time | Asia/Dhaka | +06:00 | No | +| Nepal Standard Time | Nepal Standard Time | Asia/Katmandu | +05:45 | No | +| SE Asia Standard Time | SE Asia Standard Time | Asia/Bangkok | +07:00 | No | +| Singapore Standard Time | Singapore Standard Time | Asia/Singapore | +08:00 | No | +| China Standard Time | China Standard Time | Asia/Shanghai | +08:00 | No | +| Tokyo Standard Time | Tokyo Standard Time | Asia/Tokyo | +09:00 | No | +| Korea Standard Time | Korea Standard Time | Asia/Seoul | +09:00 | No | +| AUS Eastern Standard Time | AUS Eastern Standard Time | Australia/Sydney | +10:00/+11:00 | Yes | +| New Zealand Standard Time | New Zealand Standard Time | Pacific/Auckland | +12:00/+13:00 | Yes | +| Arabian Standard Time | Arabian Standard Time | Asia/Dubai | +04:00 | No | +| Arab Standard Time | Arab Standard Time | Asia/Riyadh | +03:00 | No | +| Israel Standard Time | Israel Standard Time | Asia/Jerusalem | +02:00/+03:00 | Yes | +| Turkey Standard Time | Turkey Standard Time | Europe/Istanbul | +03:00 | No | + +### Europe + +| Display Name | Windows ID | IANA ID | UTC Offset | DST? | +| --- | --- | --- | --- | --- | +| UTC | UTC | Etc/UTC | +00:00 | No | +| GMT Standard Time | GMT Standard Time | Europe/London | +00:00/+01:00 | Yes | +| W. Europe Standard Time | W. Europe Standard Time | Europe/Berlin | +01:00/+02:00 | Yes | +| Central Europe Standard Time | Central Europe Standard Time | Europe/Budapest | +01:00/+02:00 | Yes | +| Romance Standard Time | Romance Standard Time | Europe/Paris | +01:00/+02:00 | Yes | +| E. Europe Standard Time | E. Europe Standard Time | Asia/Nicosia | +02:00/+03:00 | Yes | +| GTB Standard Time | GTB Standard Time | Europe/Bucharest | +02:00/+03:00 | Yes | +| Russian Standard Time | Russian Standard Time | Europe/Moscow | +03:00 | No | + +### Americas + +| Display Name | Windows ID | IANA ID | UTC Offset | DST? | +| --- | --- | --- | --- | --- | +| Eastern Standard Time | Eastern Standard Time | America/New_York | -05:00/-04:00 | Yes | +| Central Standard Time | Central Standard Time | America/Chicago | -06:00/-05:00 | Yes | +| Mountain Standard Time | Mountain Standard Time | America/Denver | -07:00/-06:00 | Yes | +| Pacific Standard Time | Pacific Standard Time | America/Los_Angeles | -08:00/-07:00 | Yes | +| Alaskan Standard Time | Alaskan Standard Time | America/Anchorage | -09:00/-08:00 | Yes | +| Hawaiian Standard Time | Hawaiian Standard Time | Pacific/Honolulu | -10:00 | No | +| Canada Central Standard Time | Canada Central Standard Time | America/Regina | -06:00 | No | +| SA Eastern Standard Time | SA Eastern Standard Time | America/Cayenne | -03:00 | No | +| E. South America Standard Time | E. South America Standard Time | America/Sao_Paulo | -03:00/-02:00 | Yes | + +### Africa + +| Display Name | Windows ID | IANA ID | UTC Offset | DST? | +| --- | --- | --- | --- | --- | +| South Africa Standard Time | South Africa Standard Time | Africa/Johannesburg | +02:00 | No | +| Egypt Standard Time | Egypt Standard Time | Africa/Cairo | +02:00 | No | +| E. Africa Standard Time | E. Africa Standard Time | Africa/Nairobi | +03:00 | No | +| W. Central Africa Standard Time | W. Central Africa Standard Time | Africa/Lagos | +01:00 | No | +| Morocco Standard Time | Morocco Standard Time | Africa/Casablanca | +00:00/+01:00 | Yes | + +## NodaTime Providers + +```csharp +DateTimeZoneProviders.Tzdb["Asia/Colombo"] +DateTimeZoneProviders.Bcl["Sri Lanka Standard Time"] +``` + +## TimeZoneConverter Examples + +```csharp +string ianaId = TZConvert.WindowsToIana("Sri Lanka Standard Time"); +string windowsId = TZConvert.IanaToWindows("Asia/Colombo"); +TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Asia/Colombo"); +``` + +## Programmatic Discovery + +```csharp +foreach (var tz in TimeZoneInfo.GetSystemTimeZones()) +{ + Console.WriteLine($"ID: {tz.Id} | Display: {tz.DisplayName}"); +} +```