Skip to content

Commit

Permalink
feat(os): migrate from Windows to Ubuntu linux and add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ashtonav committed Dec 15, 2024
1 parent fce557d commit 485415b
Show file tree
Hide file tree
Showing 25 changed files with 1,160 additions and 468 deletions.
5 changes: 5 additions & 0 deletions src/Timezone.Core/Extensions/MappingExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ public static class MappingExtensions
BaseOffset = timezone.BaseUtcOffset
};

public static TimeZoneConversionResultResponse ToResponse(this TimeZoneConversionResultDomain domain) => new()
{
DateTime = domain.DateTime
};

public static IEnumerable<TimezoneResponse> ToResponse(this IEnumerable<TimeZoneInfo> timezones) =>
timezones.Select(x => x.ToResponse());
}
6 changes: 6 additions & 0 deletions src/Timezone.Core/Models/TimeZoneConversionResultDomain.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Timezone.Core.Models;

public class TimeZoneConversionResultDomain
{
public DateTime DateTime { get; set; }
}
6 changes: 6 additions & 0 deletions src/Timezone.Core/Models/TimeZoneConversionResultResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Timezone.Core.Models;

public class TimeZoneConversionResultResponse
{
public DateTime DateTime { get; set; }
}
11 changes: 0 additions & 11 deletions src/Timezone.Core/Services/DateTimeExtensions.cs

This file was deleted.

2 changes: 1 addition & 1 deletion src/Timezone.Core/Services/ITimezoneService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Timezone.Core.Services;

public interface ITimezoneService
{
DateTime ConvertFromOneTimezoneToAnother(TimeZoneConversionDomain request);
TimeZoneConversionResultDomain ConvertFromOneTimezoneToAnother(TimeZoneConversionDomain request);
IEnumerable<TimeZoneInfo> GetTimezones();
TimeZoneInfo? GetTimezone(string timezoneId);
}
24 changes: 15 additions & 9 deletions src/Timezone.Core/Services/TimezoneService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@ public class TimezoneService : ITimezoneService
{
public IEnumerable<TimeZoneInfo> GetTimezones() => TimeZoneInfo.GetSystemTimeZones();

public TimeZoneInfo? GetTimezone(string timezoneId)
{
return TimezoneHelper.GetTimezone(timezoneId);
}
public TimeZoneInfo? GetTimezone(string timezoneId) => TimezoneHelper.GetTimezone(timezoneId);

public DateTime ConvertFromOneTimezoneToAnother(TimeZoneConversionDomain request)
public TimeZoneConversionResultDomain ConvertFromOneTimezoneToAnother(TimeZoneConversionDomain request)
{
ValidateRequest(request);

var dateTime = DateTime.SpecifyKind(request.DateTime, DateTimeKind.Unspecified);
var dateTimeUtc = dateTime.ConvertToUtc(request.FromTimezone);
var dateTimeConverted = TimeZoneInfo.ConvertTimeFromUtc(dateTimeUtc, request.ToTimezone);
var conversionResult = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(
SanitizeDateTime(request.DateTime),
request.FromTimezone.Id,
request.ToTimezone.Id);

return dateTimeConverted;
return new TimeZoneConversionResultDomain { DateTime = SanitizeDateTime(conversionResult) };
}

private static void ValidateRequest(TimeZoneConversionDomain request)
Expand All @@ -39,4 +37,12 @@ private static void ValidateRequest(TimeZoneConversionDomain request)
throw new ArgumentException("ToTimezone provided is invalid. Please provide a valid timezone.");
}
}

/// <summary>
/// This sanitizes the DateTime object by removing the DateTimeKind.
/// This is necessary because the TimeZoneInfo.ConvertTimeBySystemTimeZoneId method
/// does not work correctly with DateTimeKind.Local or DateTimeKind.Utc.
/// Additionally, this method removes "Z" from the end of the DateTime string.
/// </summary>
private static DateTime SanitizeDateTime(DateTime dateTime) => DateTime.SpecifyKind(dateTime, DateTimeKind.Unspecified);
}
11 changes: 3 additions & 8 deletions src/Timezone.Core/Timezone.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,9 @@
</PropertyGroup>

<ItemGroup>
<Compile Remove="Handler\**"/>
<EmbeddedResource Remove="Handler\**"/>
<None Remove="Handler\**"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.1" />
<PackageReference Include="System.Linq.Async" Version="6.0.1"/>
<Compile Remove="Handler\**" />
<EmbeddedResource Remove="Handler\**" />
<None Remove="Handler\**" />
</ItemGroup>

</Project>
8 changes: 4 additions & 4 deletions src/Timezone.WebApi/Controllers/ConvertController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ namespace TimezoneWebApi.Controllers;
public class ConvertController(ITimezoneService timezoneService) : Controller
{
[HttpPost]
[ProducesResponseType(StatusCodes.Status200OK, Type= typeof(DateTime))]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(DateTime))]
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorModel))]
[SwaggerRequestExample(typeof(TimeZoneConversionRequest), typeof(PostConvertExamples))]
public async Task<DateTime> ConvertTime(TimeZoneConversionRequest request)
public async Task<TimeZoneConversionResultResponse> ConvertTime(TimeZoneConversionRequest request)
{
var domain = request.ToDomain();

var response = timezoneService.ConvertFromOneTimezoneToAnother(domain);
var conversionResult = timezoneService.ConvertFromOneTimezoneToAnother(domain);

return response;
return conversionResult.ToResponse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public IEnumerable<SwaggerExample<TimeZoneConversionRequest>> GetExamples()
"Example 1 - Convert UTC to New York Time",
new TimeZoneConversionRequest
{
DateTime = DateTime.UtcNow,
DateTime = TimeProvider.System.GetUtcNow().DateTime,
FromTimezone = "UTC",
ToTimezone = "America/New_York"
});
Expand All @@ -21,7 +21,7 @@ public IEnumerable<SwaggerExample<TimeZoneConversionRequest>> GetExamples()
"Example 2 - Convert New York Time to UTC",
new TimeZoneConversionRequest
{
DateTime = TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.FindSystemTimeZoneById("America/New_York")),
DateTime = TimeZoneInfo.ConvertTime(TimeProvider.System.GetUtcNow().DateTime, TimeZoneInfo.FindSystemTimeZoneById("America/New_York")),
FromTimezone = "America/New_York",
ToTimezone = "UTC"
});
Expand All @@ -31,7 +31,7 @@ public IEnumerable<SwaggerExample<TimeZoneConversionRequest>> GetExamples()
"Example 3 - Convert from one timezone to another timezone",
new TimeZoneConversionRequest
{
DateTime = TimeZoneInfo.ConvertTime(DateTime.UtcNow, TimeZoneInfo.FindSystemTimeZoneById("America/New_York")),
DateTime = TimeZoneInfo.ConvertTime(TimeProvider.System.GetUtcNow().DateTime, TimeZoneInfo.FindSystemTimeZoneById("America/New_York")),
FromTimezone = "America/New_York",
ToTimezone = "Europe/London"
});
Expand Down
6 changes: 3 additions & 3 deletions src/Timezone.WebApi/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# Using Ubuntu Linux, because it is a full version of Linux
# I would stay away from smaller versions of Linux, like Alpine, as we retrieve timezones from Operating System
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble AS base

# The rest of Dockerfile has been generated by Visual Studio
# See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.

# This stage is used when running from VS in fast mode (Default for Debug configuration)
FROM mcr.microsoft.com/dotnet/aspnet:8.0-noble AS base
USER app
WORKDIR /app
EXPOSE 8080
EXPOSE 8081


# This stage is used to build the service project
FROM mcr.microsoft.com/dotnet/sdk:8.0-noble AS build
ARG BUILD_CONFIGURATION=Release
Expand Down
6 changes: 6 additions & 0 deletions src/Timezone.WebApi/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
using System.Runtime.InteropServices;
using TimezoneWebApi.Dependency;

if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
throw new PlatformNotSupportedException("Currently, this application is designed to work exclusively on Linux/Docker due to its specific timezone support. Support for other operating systems is not yet provided.");
}

var builder = WebApplication.CreateBuilder(args);

var app = builder
Expand Down
9 changes: 0 additions & 9 deletions src/Timezone.WebApi/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,5 @@
{
"profiles": {
"https": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"dotnetRunMessages": true,
"applicationUrl": "https://localhost:5280"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
Expand Down
18 changes: 7 additions & 11 deletions src/Timezone.WebApi/TimezoneWebApi.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Expand All @@ -12,18 +12,14 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="8.0.10" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="8.0.2" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="System.Linq.Async" Version="6.0.1" />
<PackageReference Include="System.Linq.Async.Queryable" Version="6.0.1" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.10.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.10.1" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.10.0" />
</ItemGroup>

<ItemGroup>
Expand Down
18 changes: 18 additions & 0 deletions tests/Timezone.FunctionalTests/Features/ConvertController.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
Feature: Timezone Conversion
As an API user,
I want to ensure that the timezone conversion API is accurate
So that I can reliably convert date and time across different timezones

Scenario Outline: Requesting to convert time from one timezone to another timezone should return the correct time
Given I have a request to convert date '<DateTime>' from '<FromTimezone>' to '<ToTimezone>'
When I send the request
Then the response should return '200' status code
And the response should correctly convert the time into '<ExpectedDateTime>'

Examples:
| DateTime | FromTimezone | ToTimezone | ExpectedDateTime |
| "2023-02-01T12:00:00" | "UTC" | "America/New_York" | "2023-02-01T07:00:00" |
| "2023-02-01T07:00:00" | "America/New_York" | "UTC" | "2023-02-01T12:00:00" |
| "2023-02-01T07:00:00" | "America/New_York" | "Europe/London" | "2023-02-01T12:00:00" |
| "2023-02-01T07:00:00" | "America/New_York" | "Asia/Tokyo" | "2023-02-01T21:00:00" |

43 changes: 0 additions & 43 deletions tests/Timezone.FunctionalTests/Features/Timezone.feature

This file was deleted.

24 changes: 24 additions & 0 deletions tests/Timezone.FunctionalTests/Features/TimezoneController.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
Feature: Timezone Retrieval
As an API user,
I want to be able to interact with the timezone data,
So that I can retrieve all timezones, request specific timezone information, and receive proper error handling for invalid timezone requests.

Scenario: Requesting all timezones should return all timezones
Given I have a request to get all timezones
When I send the request
Then the response should return '200' status code
# As of 15th of December 2024, there are 419 timezones.
Then I should get at least 400 timezones

Scenario: Request one valid timezone should return one timezone
Given I have a request to get 'America/New_York' timezone
When I send the request
Then the response should return '200' status code
Then the response should contain timezone information:
| Name | Offset |
| (UTC-05:00) America/New_York | -5 |

Scenario: Request one invalid timezone should return 404
Given I have a request to get 'invalid-timezone' timezone
When I send the request
Then the response should return '404' status code
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
namespace Timezone.FunctionalTests.StepDefinitions;

using System.Globalization;
using System.Text.Json;
using Core.Models;
using FluentAssertions;
using RestSharp;
using Support;

[Binding]
public class ConvertStepDefinitions(ScenarioContext context) : TestBase(context)
{
private static JsonSerializerOptions JsonSerializerOptions => new()
{
PropertyNameCaseInsensitive = true,
};

[Given(@"I have a request to convert date '""([^""]*)""' from '""([^""]*)""' to '""([^""]*)""'")]
public void GivenIHaveARequestToConvertDateFromTo(string dateTimeString, string fromTimezone, string toTimezone)
{
var request = new RestRequest("/convert", Method.Post) { RequestFormat = DataFormat.Json };

request.AddBody(new TimeZoneConversionRequest
{
DateTime = DateTime.Parse(dateTimeString, CultureInfo.InvariantCulture),
FromTimezone = fromTimezone,
ToTimezone = toTimezone
});

// Add to context
Context.Set(request);
}

[Then(@"the response should correctly convert the time into '""([^""]*)""'")]
public void ThenTheResponseShouldCorrectlyConvertTheTimeInto(string dateTime)
{
// Arrange
var expected = DateTime.Parse(dateTime, CultureInfo.InvariantCulture);

// Act
var rawResponse = Context.Get<RestResponse>();
var response = JsonSerializer.Deserialize<TimeZoneConversionResultResponse>
(rawResponse.Content, JsonSerializerOptions);

response.DateTime.Should().Be(expected);
}
}
Loading

0 comments on commit 485415b

Please sign in to comment.