Skip to content

Commit b3de201

Browse files
feat: add .NET timezone handling guidance and reference materials (#1123)
* feat: add .NET timezone handling guidance and reference materials * feat: update Finland, Lithuania, Estonia timezone reference in index * feat: remove Finland, Lithuania, Estonia timezone reference from index
1 parent dc66a73 commit b3de201

4 files changed

Lines changed: 350 additions & 0 deletions

File tree

docs/README.skills.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to
110110
| [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 |
111111
| [dotnet-best-practices](../skills/dotnet-best-practices/SKILL.md) | Ensure .NET/C# code meets best practices for the solution/project. | None |
112112
| [dotnet-design-pattern-review](../skills/dotnet-design-pattern-review/SKILL.md) | Review the C#/.NET code for design pattern implementation and suggest improvements. | None |
113+
| [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`<br />`references/timezone-index.md` |
113114
| [dotnet-upgrade](../skills/dotnet-upgrade/SKILL.md) | Ready-to-use prompts for comprehensive .NET framework upgrade analysis and execution | None |
114115
| [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` |
115116
| [editorconfig](../skills/editorconfig/SKILL.md) | Generates a comprehensive and best-practice-oriented .editorconfig file based on project analysis and user preferences. | None |

skills/dotnet-timezone/SKILL.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
name: dotnet-timezone
3+
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.'
4+
---
5+
6+
# .NET Timezone
7+
8+
Resolve timezone questions for .NET and C# code with production-safe guidance and copy-paste-ready snippets.
9+
10+
## Start With The Right Path
11+
12+
Identify the request type first:
13+
14+
- Address or location lookup
15+
- Timezone ID lookup
16+
- UTC/local conversion
17+
- Cross-platform timezone compatibility
18+
- Scheduling or DST handling
19+
- API or persistence design
20+
21+
If the library is unclear, default to `TimeZoneConverter` for cross-platform work. If the scenario involves recurring schedules or strict DST rules, prefer `NodaTime`.
22+
23+
## Resolve Addresses And Locations
24+
25+
If the user provides an address, city, region, country, or document containing place names:
26+
27+
1. Extract each location from the input.
28+
2. Read `references/timezone-index.md` for common Windows and IANA mappings.
29+
3. If the exact location is not listed, infer the correct IANA zone from geography, then map it to the Windows ID.
30+
4. Return both IDs and a ready-to-use C# example.
31+
32+
For each resolved location, provide:
33+
34+
```text
35+
Location: <resolved place>
36+
Windows ID: <windows id>
37+
IANA ID: <iana id>
38+
UTC offset: <standard offset and DST offset when relevant>
39+
DST: <yes/no>
40+
```
41+
42+
Then include a cross-platform snippet like:
43+
44+
```csharp
45+
using TimeZoneConverter;
46+
47+
TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Asia/Colombo");
48+
DateTime local = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
49+
```
50+
51+
If multiple locations are present, include one block per location and then a combined multi-timezone snippet.
52+
53+
If a location is ambiguous, list the possible timezone matches and ask the user to choose the correct one.
54+
55+
## Look Up Timezone IDs
56+
57+
Use `references/timezone-index.md` for Windows to IANA mappings.
58+
59+
Always provide both formats:
60+
61+
- Windows ID for `TimeZoneInfo.FindSystemTimeZoneById()` on Windows
62+
- IANA ID for Linux, containers, `NodaTime`, and `TimeZoneConverter`
63+
64+
## Generate Code
65+
66+
Use `references/code-patterns.md` and pick the smallest pattern that fits:
67+
68+
- Pattern 1: `TimeZoneInfo` for Windows-only code
69+
- Pattern 2: `TimeZoneConverter` for cross-platform conversion
70+
- Pattern 3: `NodaTime` for strict timezone arithmetic and DST-sensitive scheduling
71+
- Pattern 4: `DateTimeOffset` for APIs and data transfer
72+
- Pattern 5: ASP.NET Core persistence and presentation
73+
- Pattern 6: recurring jobs and schedulers
74+
- Pattern 7: ambiguous and invalid DST timestamps
75+
76+
Always include package guidance when recommending third-party libraries.
77+
78+
## Warn About Common Pitfalls
79+
80+
Mention the relevant warning when applicable:
81+
82+
- `TimeZoneInfo.FindSystemTimeZoneById()` is platform-specific for timezone IDs.
83+
- Avoid storing `DateTime.Now` in databases; store UTC instead.
84+
- Treat `DateTimeKind.Unspecified` as a bug risk unless it is deliberate input.
85+
- DST transitions can skip or repeat local times.
86+
- Azure Windows and Azure Linux environments may expect different timezone ID formats.
87+
88+
## Response Shape
89+
90+
For address and location requests:
91+
92+
1. Return the resolved timezone block for each location.
93+
2. State the recommended implementation in one sentence.
94+
3. Include a copy-paste-ready C# snippet.
95+
96+
For code and architecture requests:
97+
98+
1. State the recommended approach in one sentence.
99+
2. Provide the timezone IDs if relevant.
100+
3. Include the minimal working code snippet.
101+
4. Mention the package requirement if needed.
102+
5. Add one pitfall warning if it matters.
103+
104+
Keep responses concise and code-first.
105+
106+
## References
107+
108+
- `references/timezone-index.md`: common Windows and IANA timezone mappings
109+
- `references/code-patterns.md`: ready-to-use .NET timezone patterns
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
# .NET Timezone Code Patterns
2+
3+
## Pattern 1: Basic TimeZoneInfo
4+
5+
Use this only when the application is Windows-only and Windows timezone IDs are acceptable.
6+
7+
```csharp
8+
DateTime utcNow = DateTime.UtcNow;
9+
TimeZoneInfo sriLankaTz = TimeZoneInfo.FindSystemTimeZoneById("Sri Lanka Standard Time");
10+
DateTime localTime = TimeZoneInfo.ConvertTimeFromUtc(utcNow, sriLankaTz);
11+
12+
DateTime backToUtc = TimeZoneInfo.ConvertTimeToUtc(localTime, sriLankaTz);
13+
14+
TimeZoneInfo tokyoTz = TimeZoneInfo.FindSystemTimeZoneById("Tokyo Standard Time");
15+
DateTime tokyoTime = TimeZoneInfo.ConvertTime(localTime, sriLankaTz, tokyoTz);
16+
```
17+
18+
Use `TimeZoneConverter` or `NodaTime` instead for Linux, containers, or mixed environments.
19+
20+
## Pattern 2: Cross-Platform With TimeZoneConverter
21+
22+
Recommended default for most .NET apps that run across Windows and Linux.
23+
24+
```xml
25+
<PackageReference Include="TimeZoneConverter" Version="6.*" />
26+
```
27+
28+
```csharp
29+
using TimeZoneConverter;
30+
31+
TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Asia/Colombo");
32+
DateTime converted = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, tz);
33+
```
34+
35+
This also accepts Windows IDs:
36+
37+
```csharp
38+
TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Sri Lanka Standard Time");
39+
```
40+
41+
## Pattern 3: NodaTime
42+
43+
Use this for strict timezone arithmetic, recurring schedules, or DST edge cases where correctness matters more than minimal dependencies.
44+
45+
```xml
46+
<PackageReference Include="NodaTime" Version="3.*" />
47+
```
48+
49+
```csharp
50+
using NodaTime;
51+
52+
DateTimeZone colomboZone = DateTimeZoneProviders.Tzdb["Asia/Colombo"];
53+
Instant now = SystemClock.Instance.GetCurrentInstant();
54+
ZonedDateTime colomboTime = now.InZone(colomboZone);
55+
56+
DateTimeZone tokyoZone = DateTimeZoneProviders.Tzdb["Asia/Tokyo"];
57+
ZonedDateTime tokyoTime = colomboTime.WithZone(tokyoZone);
58+
59+
LocalDateTime localDt = new LocalDateTime(2024, 6, 15, 14, 30, 0);
60+
ZonedDateTime zoned = colomboZone.AtStrictly(localDt);
61+
Instant utcInstant = zoned.ToInstant();
62+
```
63+
64+
## Pattern 4: DateTimeOffset For APIs
65+
66+
Prefer `DateTimeOffset` for values crossing service or process boundaries.
67+
68+
```csharp
69+
using TimeZoneConverter;
70+
71+
DateTimeOffset utcNow = DateTimeOffset.UtcNow;
72+
TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Asia/Colombo");
73+
DateTimeOffset colomboTime = TimeZoneInfo.ConvertTime(utcNow, tz);
74+
```
75+
76+
## Pattern 5: ASP.NET Core Persistence And Presentation
77+
78+
Store UTC, convert at the edges.
79+
80+
```csharp
81+
using TimeZoneConverter;
82+
83+
entity.CreatedAtUtc = DateTime.UtcNow;
84+
85+
public DateTimeOffset ToUserTime(DateTime utc, string userIanaTimezone)
86+
{
87+
var tz = TZConvert.GetTimeZoneInfo(userIanaTimezone);
88+
return TimeZoneInfo.ConvertTimeFromUtc(utc, tz);
89+
}
90+
```
91+
92+
## Pattern 6: Scheduling And Recurring Jobs
93+
94+
Translate a user-facing local time to UTC before scheduling.
95+
96+
```csharp
97+
using TimeZoneConverter;
98+
99+
TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Asia/Colombo");
100+
DateTime scheduledLocal = new DateTime(2024, 12, 1, 9, 0, 0, DateTimeKind.Unspecified);
101+
DateTime scheduledUtc = TimeZoneInfo.ConvertTimeToUtc(scheduledLocal, tz);
102+
```
103+
104+
With Hangfire:
105+
106+
```csharp
107+
RecurringJob.AddOrUpdate(
108+
"morning-job",
109+
() => DoWork(),
110+
"0 9 * * *",
111+
new RecurringJobOptions { TimeZone = tz });
112+
```
113+
114+
## Pattern 7: Ambiguous And Invalid DST Times
115+
116+
Check for repeated or skipped local timestamps when the timezone observes daylight saving time.
117+
118+
```csharp
119+
using TimeZoneConverter;
120+
121+
TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("America/New_York");
122+
DateTime localTime = new DateTime(2024, 11, 3, 1, 30, 0);
123+
124+
if (tz.IsAmbiguousTime(localTime))
125+
{
126+
var offsets = tz.GetAmbiguousTimeOffsets(localTime);
127+
var standardOffset = offsets.Min();
128+
var dto = new DateTimeOffset(localTime, standardOffset);
129+
}
130+
131+
if (tz.IsInvalidTime(localTime))
132+
{
133+
localTime = localTime.AddHours(1);
134+
}
135+
```
136+
137+
## Common Mistakes
138+
139+
| Wrong | Better |
140+
| --- | --- |
141+
| `DateTime.Now` in server code | `DateTime.UtcNow` |
142+
| Storing local timestamps in the database | Store UTC and convert for display |
143+
| Hardcoding offsets such as `+05:30` | Use timezone IDs |
144+
| Using `FindSystemTimeZoneById("Asia/Colombo")` on Windows | Use `TZConvert.GetTimeZoneInfo("Asia/Colombo")` |
145+
| Comparing local `DateTime` values from different zones | Compare UTC or use `DateTimeOffset` |
146+
| Creating `DateTime` without intentional kind semantics | Use `Utc`, `Local`, or deliberate `Unspecified` |
147+
148+
## Decision Guide
149+
150+
- Use `TimeZoneInfo` only for Windows-only code with Windows IDs.
151+
- Use `TimeZoneConverter` for most cross-platform applications.
152+
- Use `NodaTime` when DST arithmetic or calendaring accuracy is central.
153+
- Use `DateTimeOffset` for APIs and serialized timestamps.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# .NET Timezone Reference Index
2+
3+
## Windows To IANA Mapping
4+
5+
Use this file for common mappings between Windows timezone IDs and IANA timezone IDs.
6+
7+
### Asia And Pacific
8+
9+
| Display Name | Windows ID | IANA ID | UTC Offset | DST? |
10+
| --- | --- | --- | --- | --- |
11+
| Sri Lanka Standard Time | Sri Lanka Standard Time | Asia/Colombo | +05:30 | No |
12+
| India Standard Time | India Standard Time | Asia/Calcutta | +05:30 | No |
13+
| Pakistan Standard Time | Pakistan Standard Time | Asia/Karachi | +05:00 | No |
14+
| Bangladesh Standard Time | Bangladesh Standard Time | Asia/Dhaka | +06:00 | No |
15+
| Nepal Standard Time | Nepal Standard Time | Asia/Katmandu | +05:45 | No |
16+
| SE Asia Standard Time | SE Asia Standard Time | Asia/Bangkok | +07:00 | No |
17+
| Singapore Standard Time | Singapore Standard Time | Asia/Singapore | +08:00 | No |
18+
| China Standard Time | China Standard Time | Asia/Shanghai | +08:00 | No |
19+
| Tokyo Standard Time | Tokyo Standard Time | Asia/Tokyo | +09:00 | No |
20+
| Korea Standard Time | Korea Standard Time | Asia/Seoul | +09:00 | No |
21+
| AUS Eastern Standard Time | AUS Eastern Standard Time | Australia/Sydney | +10:00/+11:00 | Yes |
22+
| New Zealand Standard Time | New Zealand Standard Time | Pacific/Auckland | +12:00/+13:00 | Yes |
23+
| Arabian Standard Time | Arabian Standard Time | Asia/Dubai | +04:00 | No |
24+
| Arab Standard Time | Arab Standard Time | Asia/Riyadh | +03:00 | No |
25+
| Israel Standard Time | Israel Standard Time | Asia/Jerusalem | +02:00/+03:00 | Yes |
26+
| Turkey Standard Time | Turkey Standard Time | Europe/Istanbul | +03:00 | No |
27+
28+
### Europe
29+
30+
| Display Name | Windows ID | IANA ID | UTC Offset | DST? |
31+
| --- | --- | --- | --- | --- |
32+
| UTC | UTC | Etc/UTC | +00:00 | No |
33+
| GMT Standard Time | GMT Standard Time | Europe/London | +00:00/+01:00 | Yes |
34+
| W. Europe Standard Time | W. Europe Standard Time | Europe/Berlin | +01:00/+02:00 | Yes |
35+
| Central Europe Standard Time | Central Europe Standard Time | Europe/Budapest | +01:00/+02:00 | Yes |
36+
| Romance Standard Time | Romance Standard Time | Europe/Paris | +01:00/+02:00 | Yes |
37+
| E. Europe Standard Time | E. Europe Standard Time | Asia/Nicosia | +02:00/+03:00 | Yes |
38+
| GTB Standard Time | GTB Standard Time | Europe/Bucharest | +02:00/+03:00 | Yes |
39+
| Russian Standard Time | Russian Standard Time | Europe/Moscow | +03:00 | No |
40+
41+
### Americas
42+
43+
| Display Name | Windows ID | IANA ID | UTC Offset | DST? |
44+
| --- | --- | --- | --- | --- |
45+
| Eastern Standard Time | Eastern Standard Time | America/New_York | -05:00/-04:00 | Yes |
46+
| Central Standard Time | Central Standard Time | America/Chicago | -06:00/-05:00 | Yes |
47+
| Mountain Standard Time | Mountain Standard Time | America/Denver | -07:00/-06:00 | Yes |
48+
| Pacific Standard Time | Pacific Standard Time | America/Los_Angeles | -08:00/-07:00 | Yes |
49+
| Alaskan Standard Time | Alaskan Standard Time | America/Anchorage | -09:00/-08:00 | Yes |
50+
| Hawaiian Standard Time | Hawaiian Standard Time | Pacific/Honolulu | -10:00 | No |
51+
| Canada Central Standard Time | Canada Central Standard Time | America/Regina | -06:00 | No |
52+
| SA Eastern Standard Time | SA Eastern Standard Time | America/Cayenne | -03:00 | No |
53+
| E. South America Standard Time | E. South America Standard Time | America/Sao_Paulo | -03:00/-02:00 | Yes |
54+
55+
### Africa
56+
57+
| Display Name | Windows ID | IANA ID | UTC Offset | DST? |
58+
| --- | --- | --- | --- | --- |
59+
| South Africa Standard Time | South Africa Standard Time | Africa/Johannesburg | +02:00 | No |
60+
| Egypt Standard Time | Egypt Standard Time | Africa/Cairo | +02:00 | No |
61+
| E. Africa Standard Time | E. Africa Standard Time | Africa/Nairobi | +03:00 | No |
62+
| W. Central Africa Standard Time | W. Central Africa Standard Time | Africa/Lagos | +01:00 | No |
63+
| Morocco Standard Time | Morocco Standard Time | Africa/Casablanca | +00:00/+01:00 | Yes |
64+
65+
## NodaTime Providers
66+
67+
```csharp
68+
DateTimeZoneProviders.Tzdb["Asia/Colombo"]
69+
DateTimeZoneProviders.Bcl["Sri Lanka Standard Time"]
70+
```
71+
72+
## TimeZoneConverter Examples
73+
74+
```csharp
75+
string ianaId = TZConvert.WindowsToIana("Sri Lanka Standard Time");
76+
string windowsId = TZConvert.IanaToWindows("Asia/Colombo");
77+
TimeZoneInfo tz = TZConvert.GetTimeZoneInfo("Asia/Colombo");
78+
```
79+
80+
## Programmatic Discovery
81+
82+
```csharp
83+
foreach (var tz in TimeZoneInfo.GetSystemTimeZones())
84+
{
85+
Console.WriteLine($"ID: {tz.Id} | Display: {tz.DisplayName}");
86+
}
87+
```

0 commit comments

Comments
 (0)