Skip to content

Commit 77941b5

Browse files
committed
add LocalTime
1 parent 6db4226 commit 77941b5

File tree

13 files changed

+590
-10
lines changed

13 files changed

+590
-10
lines changed

samples/Sample.ClientSide/Sample.ClientSide.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,10 @@
88
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
99
</PropertyGroup>
1010
<ItemGroup>
11-
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.7" />
12-
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.7" PrivateAssets="all" />
13-
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.7" />
14-
<PackageReference Include="System.Net.Http.Json" Version="9.0.7" />
11+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="9.0.8" />
12+
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="9.0.8" PrivateAssets="all" />
13+
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.8" />
14+
<PackageReference Include="System.Net.Http.Json" Version="9.0.8" />
1515
</ItemGroup>
1616
<ItemGroup>
1717
<ProjectReference Include="..\Sample.Core\Sample.Core.csproj" />
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
@page "/culture/index"
2+
@implements IDisposable
3+
4+
<h1>LocalTime</h1>
5+
6+
<p>Local Time Control - Renders date and time values in the user's local time zone.</p>
7+
8+
<Instructions></Instructions>
9+
10+
<h2 class="mb-3">Examples</h2>
11+
12+
<h3>DateTime Usage</h3>
13+
14+
<p>Basic example showing LocalTime with fixed DateTime values, displaying both original and converted local time with different DateTimeKind values.</p>
15+
16+
<div class="m-3 p-3 border rounded">
17+
<div class="row mb-3">
18+
<div class="col-sm-3"><strong>Team Meeting (Local):</strong></div>
19+
<div class="col-sm-9">
20+
<div><span class="text-muted">Original:</span> @teamMeetingDateTime.ToString("yyyy-MM-dd HH:mm:ss") (@teamMeetingDateTime.Kind)</div>
21+
<div><span class="text-muted">Local Time:</span> <LocalTime Value="@teamMeetingDateTime" /></div>
22+
<div><span class="text-muted">Date Kind:</span> @teamMeetingDateTime.Kind</div>
23+
</div>
24+
</div>
25+
<div class="row mb-3">
26+
<div class="col-sm-3"><strong>Server Log Entry (UTC):</strong></div>
27+
<div class="col-sm-9">
28+
<div><span class="text-muted">Original:</span> @serverLogDateTime.ToString("yyyy-MM-dd HH:mm:ss") (@serverLogDateTime.Kind)</div>
29+
<div><span class="text-muted">Local Time:</span> <LocalTime Value="@serverLogDateTime" /></div>
30+
<div><span class="text-muted">Date Kind:</span> @serverLogDateTime.Kind</div>
31+
</div>
32+
</div>
33+
<div class="row mb-3">
34+
<div class="col-sm-3"><strong>Historical Event (Local):</strong></div>
35+
<div class="col-sm-9">
36+
<div><span class="text-muted">Original:</span> @historicalEventDateTime.ToString("yyyy-MM-dd HH:mm:ss") (@historicalEventDateTime.Kind)</div>
37+
<div><span class="text-muted">Local Time:</span> <LocalTime Value="@historicalEventDateTime" /></div>
38+
<div><span class="text-muted">Date Kind:</span> @historicalEventDateTime.Kind</div>
39+
</div>
40+
</div>
41+
<div class="row mb-2">
42+
<div class="col-sm-3"><strong>Scheduled Task (Local):</strong></div>
43+
<div class="col-sm-9">
44+
<div><span class="text-muted">Original:</span> @scheduledTaskDateTime.ToString("yyyy-MM-dd HH:mm:ss") (@scheduledTaskDateTime.Kind)</div>
45+
<div><span class="text-muted">Local Time:</span> <LocalTime Value="@scheduledTaskDateTime" /></div>
46+
<div><span class="text-muted">Date Kind:</span> @scheduledTaskDateTime.Kind</div>
47+
</div>
48+
</div>
49+
</div>
50+
51+
<h3>DateTimeOffset Usage</h3>
52+
53+
<p>Example using DateTimeOffset values with different time zones, showing the conversion from specific time zones to the user's local time.</p>
54+
55+
<div class="m-3 p-3 border rounded">
56+
<div class="row mb-3">
57+
<div class="col-sm-3"><strong>Business Meeting (EST):</strong></div>
58+
<div class="col-sm-9">
59+
<div><span class="text-muted">Original:</span> @businessMeetingTime.ToString("yyyy-MM-dd HH:mm:ss zzz") (EST)</div>
60+
<div><span class="text-muted">Local Time:</span> <LocalTime Value="@businessMeetingTime" /></div>
61+
<div><span class="text-muted">UTC Offset:</span> @businessMeetingTime.Offset</div>
62+
</div>
63+
</div>
64+
<div class="row mb-3">
65+
<div class="col-sm-3"><strong>Conference Call (GMT):</strong></div>
66+
<div class="col-sm-9">
67+
<div><span class="text-muted">Original:</span> @conferenceCallTime.ToString("yyyy-MM-dd HH:mm:ss zzz") (GMT)</div>
68+
<div><span class="text-muted">Local Time:</span> <LocalTime Value="@conferenceCallTime" /></div>
69+
<div><span class="text-muted">UTC Offset:</span> @conferenceCallTime.Offset</div>
70+
</div>
71+
</div>
72+
<div class="row mb-3">
73+
<div class="col-sm-3"><strong>Tokyo Office Hours (JST):</strong></div>
74+
<div class="col-sm-9">
75+
<div><span class="text-muted">Original:</span> @tokyoOfficeTime.ToString("yyyy-MM-dd HH:mm:ss zzz") (JST)</div>
76+
<div><span class="text-muted">Local Time:</span> <LocalTime Value="@tokyoOfficeTime" /></div>
77+
<div><span class="text-muted">UTC Offset:</span> @tokyoOfficeTime.Offset</div>
78+
</div>
79+
</div>
80+
<div class="row mb-2">
81+
<div class="col-sm-3"><strong>Server Deploy (PST):</strong></div>
82+
<div class="col-sm-9">
83+
<div><span class="text-muted">Original:</span> @serverDeployTime.ToString("yyyy-MM-dd HH:mm:ss zzz") (PST)</div>
84+
<div><span class="text-muted">Local Time:</span> <LocalTime Value="@serverDeployTime" /></div>
85+
<div><span class="text-muted">UTC Offset:</span> @serverDeployTime.Offset</div>
86+
</div>
87+
</div>
88+
</div>
89+
90+
<h3>Custom Display Formats</h3>
91+
92+
<p>Examples showing different display and title formats with their corresponding format strings.</p>
93+
94+
<div class="m-3 p-3 border rounded">
95+
<div class="row mb-2">
96+
<div class="col-sm-3"><strong>Short Date ("d"):</strong></div>
97+
<div class="col-sm-9"><LocalTime Value="@teamMeetingDateTime" DisplayFormat="d" /></div>
98+
</div>
99+
<div class="row mb-2">
100+
<div class="col-sm-3"><strong>Long Date ("D"):</strong></div>
101+
<div class="col-sm-9"><LocalTime Value="@teamMeetingDateTime" DisplayFormat="D" /></div>
102+
</div>
103+
<div class="row mb-2">
104+
<div class="col-sm-3"><strong>Short Time ("t"):</strong></div>
105+
<div class="col-sm-9"><LocalTime Value="@teamMeetingDateTime" DisplayFormat="t" /></div>
106+
</div>
107+
<div class="row mb-2">
108+
<div class="col-sm-3"><strong>Long Time ("T"):</strong></div>
109+
<div class="col-sm-9"><LocalTime Value="@teamMeetingDateTime" DisplayFormat="T" /></div>
110+
</div>
111+
<div class="row mb-2">
112+
<div class="col-sm-3"><strong>ISO 8601 ("o"):</strong></div>
113+
<div class="col-sm-9"><LocalTime Value="@teamMeetingDateTime" DisplayFormat="o" /></div>
114+
</div>
115+
<div class="row mb-2">
116+
<div class="col-sm-3"><strong>Custom Format:</strong></div>
117+
<div class="col-sm-9">
118+
<div><LocalTime Value="@teamMeetingDateTime" DisplayFormat="MMM dd, yyyy 'at' HH:mm" /></div>
119+
<div><span class="text-muted">Format:</span> "MMM dd, yyyy 'at' HH:mm"</div>
120+
</div>
121+
</div>
122+
</div>
123+
124+
<h3>Title Format Examples</h3>
125+
126+
<p>Examples showing different title formats (hover over the times to see tooltips) with their corresponding format strings.</p>
127+
128+
<div class="m-3 p-3 border rounded">
129+
<div class="row mb-2">
130+
<div class="col-sm-3"><strong>Default Title:</strong></div>
131+
<div class="col-sm-9">
132+
<div><LocalTime Value="@teamMeetingDateTime" /></div>
133+
<div><span class="text-muted">Format:</span> (default)</div>
134+
</div>
135+
</div>
136+
<div class="row mb-2">
137+
<div class="col-sm-3"><strong>Full Date/Time ("F"):</strong></div>
138+
<div class="col-sm-9">
139+
<div><LocalTime Value="@teamMeetingDateTime" TitleFormat="F" /></div>
140+
<div><span class="text-muted">Format:</span> "F"</div>
141+
</div>
142+
</div>
143+
<div class="row mb-2">
144+
<div class="col-sm-3"><strong>No Title (""):</strong></div>
145+
<div class="col-sm-9">
146+
<div><LocalTime Value="@teamMeetingDateTime" TitleFormat="" /></div>
147+
<div><span class="text-muted">Format:</span> "" (empty string)</div>
148+
</div>
149+
</div>
150+
</div>
151+
152+
<h3>Nullable Values</h3>
153+
154+
<p>Examples showing behavior with nullable DateTime values.</p>
155+
156+
<div class="m-3 p-3 border rounded">
157+
<div class="row mb-2">
158+
<div class="col-sm-3"><strong>Null DateTime:</strong></div>
159+
<div class="col-sm-9">
160+
<LocalTime TValue="DateTime?" Value="@nullDateTime" />
161+
<span class="text-muted">(nothing rendered when null)</span>
162+
</div>
163+
</div>
164+
<div class="row mb-2">
165+
<div class="col-sm-3"><strong>Conditional Value:</strong></div>
166+
<div class="col-sm-9">
167+
<LocalTime TValue="DateTime?" Value="@(showConditional ? teamMeetingDateTime : null)" />
168+
<button class="btn btn-sm btn-outline-primary ms-2" @onclick="ToggleConditional">
169+
@(showConditional ? "Hide" : "Show")
170+
</button>
171+
</div>
172+
</div>
173+
</div>
174+
175+
<h3>Live Clock Example</h3>
176+
177+
<p>A live updating clock using LocalTime component.</p>
178+
179+
<div class="m-3 p-3 border rounded bg-light">
180+
<div class="text-center">
181+
<h4 class="mb-0">
182+
<LocalTime Value="@liveTime" DisplayFormat="F" TitleFormat="o" />
183+
</h4>
184+
<small class="text-muted">Updates every second</small>
185+
</div>
186+
</div>
187+
188+
<h3>Data Table Example</h3>
189+
190+
<p>Example showing LocalTime in a data table context.</p>
191+
192+
<div class="m-3 p-3 border rounded">
193+
<table class="table table-striped">
194+
<thead>
195+
<tr>
196+
<th>Event</th>
197+
<th>Created</th>
198+
<th>Modified</th>
199+
<th>Status</th>
200+
</tr>
201+
</thead>
202+
<tbody>
203+
@foreach (var item in sampleData)
204+
{
205+
<tr>
206+
<td>@item.Name</td>
207+
<td><LocalTime Value="@item.Created" DisplayFormat="g" /></td>
208+
<td><LocalTime Value="@item.Modified" DisplayFormat="g" /></td>
209+
<td>@item.Status</td>
210+
</tr>
211+
}
212+
</tbody>
213+
</table>
214+
</div>
215+
216+
@code {
217+
// Fixed DateTime values for consistent examples
218+
private DateTime teamMeetingDateTime = new DateTime(2024, 8, 15, 14, 30, 0, DateTimeKind.Local);
219+
private DateTime serverLogDateTime = new DateTime(2024, 8, 15, 18, 30, 0, DateTimeKind.Utc);
220+
private DateTime historicalEventDateTime = new DateTime(2024, 7, 16, 9, 15, 0, DateTimeKind.Local);
221+
private DateTime scheduledTaskDateTime = new DateTime(2024, 9, 20, 16, 45, 0, DateTimeKind.Local);
222+
private DateTime? nullDateTime = null;
223+
private bool showConditional = true;
224+
private DateTime liveTime = DateTime.Now;
225+
226+
// Fixed DateTimeOffset values with specific time zones for consistent examples
227+
private DateTimeOffset businessMeetingTime = new DateTimeOffset(2024, 8, 15, 14, 30, 0, TimeSpan.FromHours(-5)); // EST (UTC-5)
228+
private DateTimeOffset conferenceCallTime = new DateTimeOffset(2024, 8, 15, 19, 30, 0, TimeSpan.Zero); // GMT (UTC+0)
229+
private DateTimeOffset tokyoOfficeTime = new DateTimeOffset(2024, 8, 16, 4, 30, 0, TimeSpan.FromHours(9)); // JST (UTC+9)
230+
private DateTimeOffset serverDeployTime = new DateTimeOffset(2024, 8, 15, 11, 30, 0, TimeSpan.FromHours(-8)); // PST (UTC-8)
231+
232+
private List<SampleEvent> sampleData = new();
233+
private Timer timer;
234+
235+
protected override void OnInitialized()
236+
{
237+
// Initialize sample data with fixed DateTime values
238+
sampleData = new List<SampleEvent>
239+
{
240+
new SampleEvent { Name = "User Registration", Created = new DateTime(2024, 8, 15, 12, 30, 0), Modified = new DateTime(2024, 8, 15, 14, 00, 0), Status = "Completed" },
241+
new SampleEvent { Name = "Order Processed", Created = new DateTime(2024, 8, 15, 9, 30, 0), Modified = new DateTime(2024, 8, 15, 13, 30, 0), Status = "Shipped" },
242+
new SampleEvent { Name = "Data Import", Created = new DateTime(2024, 8, 13, 14, 30, 0), Modified = new DateTime(2024, 8, 13, 14, 30, 0), Status = "Pending" },
243+
new SampleEvent { Name = "System Backup", Created = new DateTime(2024, 8, 14, 14, 30, 0), Modified = new DateTime(2024, 8, 15, 8, 30, 0), Status = "Success" }
244+
};
245+
246+
// Start live clock timer
247+
timer = new Timer(UpdateLiveTime, null, TimeSpan.Zero, TimeSpan.FromSeconds(1));
248+
}
249+
250+
private void UpdateLiveTime(object state)
251+
{
252+
liveTime = DateTime.Now;
253+
InvokeAsync(StateHasChanged);
254+
}
255+
256+
private void ToggleConditional()
257+
{
258+
showConditional = !showConditional;
259+
}
260+
261+
public void Dispose()
262+
{
263+
timer?.Dispose();
264+
}
265+
266+
private class SampleEvent
267+
{
268+
public string Name { get; set; } = string.Empty;
269+
public DateTime Created { get; set; }
270+
public DateTime Modified { get; set; }
271+
public string Status { get; set; } = string.Empty;
272+
}
273+
}

samples/Sample.Core/Shared/Instructions.razor

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<h2>Installing</h2>
1+
<h2>Installing</h2>
22

33
<p>You can install from NuGet using the following command:</p>
44

@@ -14,3 +14,6 @@
1414

1515
<pre><code class="language-markup">&lt;link rel=&quot;stylesheet&quot; href=&quot;_content/LoreSoft.Blazor.Controls/BlazorControls.css&quot; /&gt;</code></pre>
1616

17+
<p>At the end of the body tag</p>
18+
19+
<pre><code class="language-markup">&lt;script src=&quot;/_content/LoreSoft.Blazor.Controls/BlazorControls.js&quot;&gt;&lt;/script&gt;</code></pre>

samples/Sample.Core/Shared/MainMenu.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
<NavLink class="dropdown-item" href="/busybutton/index" Match="NavLinkMatch.Prefix">BusyButton</NavLink>
3939
<NavLink class="dropdown-item" href="/repeater/index" Match="NavLinkMatch.Prefix">Repeater</NavLink>
4040
<NavLink class="dropdown-item" href="/inputimage/index" Match="NavLinkMatch.Prefix">InputImage</NavLink>
41+
<NavLink class="dropdown-item" href="/culture/index" Match="NavLinkMatch.Prefix">LocalTime</NavLink>
4142
</div>
4243
</li>
4344
</ul>
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System.Globalization;
2+
3+
using Microsoft.JSInterop;
4+
5+
namespace LoreSoft.Blazor.Controls;
6+
7+
/// <summary>
8+
/// Provides functionality to retrieve and cache the browser's culture and time zone information.
9+
/// This service uses JavaScript interop to detect the user's language preference and local time zone from the browser.
10+
/// </summary>
11+
public class BrowserCultureProvider(IJSRuntime javaScript)
12+
{
13+
private readonly IJSRuntime _javaScript = javaScript ?? throw new ArgumentNullException(nameof(javaScript));
14+
15+
private TimeZoneInfo? _cachedTimeZone;
16+
private string? _cachedLanguage;
17+
18+
/// <summary>
19+
/// Asynchronously retrieves the browser's time zone information.
20+
/// The result is cached after the first successful retrieval unless forced to refresh.
21+
/// </summary>
22+
/// <param name="force">
23+
/// If <c>true</c>, forces a fresh retrieval from the browser, bypassing the cache.
24+
/// If <c>false</c> (default), returns the cached value if available.
25+
/// </param>
26+
/// <returns>
27+
/// A <see cref="ValueTask{TimeZoneInfo}"/> representing the asynchronous operation.
28+
/// Returns the browser's time zone if successfully detected, otherwise returns <see cref="TimeZoneInfo.Local"/>.
29+
/// </returns>
30+
/// <exception cref="JSException">
31+
/// Thrown when JavaScript interop fails to execute the browser time zone detection.
32+
/// </exception>
33+
public async ValueTask<TimeZoneInfo> GetTimeZone(bool force = false)
34+
{
35+
if (!force && _cachedTimeZone is not null)
36+
return _cachedTimeZone;
37+
38+
var browserTimeZone = await _javaScript.InvokeAsync<string>("BlazorControls.browserTimeZone");
39+
if (string.IsNullOrWhiteSpace(browserTimeZone))
40+
{
41+
_cachedTimeZone = TimeZoneInfo.Local;
42+
return TimeZoneInfo.Local;
43+
}
44+
45+
if (TimeZoneInfo.TryFindSystemTimeZoneById(browserTimeZone, out var timeZone))
46+
{
47+
_cachedTimeZone = timeZone;
48+
return timeZone;
49+
}
50+
51+
_cachedTimeZone = TimeZoneInfo.Local;
52+
return TimeZoneInfo.Local;
53+
}
54+
55+
/// <summary>
56+
/// Asynchronously retrieves the browser's language preference.
57+
/// The result is cached after the first successful retrieval unless forced to refresh.
58+
/// </summary>
59+
/// <param name="force">
60+
/// If <c>true</c>, forces a fresh retrieval from the browser, bypassing the cache.
61+
/// If <c>false</c> (default), returns the cached value if available.
62+
/// </param>
63+
/// <returns>
64+
/// A <see cref="ValueTask{String}"/> representing the asynchronous operation.
65+
/// Returns the browser's language code if successfully detected, otherwise returns <see cref="CultureInfo.CurrentUICulture"/>.
66+
/// The language code follows standard culture naming conventions (e.g., "en-US", "fr-FR").
67+
/// </returns>
68+
/// <exception cref="JSException">
69+
/// Thrown when JavaScript interop fails to execute the browser language detection.
70+
/// </exception>
71+
public async ValueTask<string> GetLanguage(bool force = false)
72+
{
73+
if (!force && _cachedLanguage is not null)
74+
return _cachedLanguage;
75+
76+
_cachedLanguage = await _javaScript.InvokeAsync<string>("BlazorControls.browserLanguage")
77+
?? CultureInfo.CurrentUICulture.Name;
78+
79+
return _cachedLanguage ?? string.Empty;
80+
}
81+
}
82+

0 commit comments

Comments
 (0)