Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support to humanizing a TimeOnly as a readable clock notation #1134

Merged
merged 12 commits into from Nov 11, 2021

Conversation

strobelt
Copy link
Contributor

Add support to humanizing a TimeOnly as a readable clock notation string as asked on #1081
Uses the Converter architecture based on the DateOnly ToOrdinal converter and extension

@strobelt strobelt changed the title Clocktime timeonly strategy Add support to humanizing a TimeOnly as a readable clock notation Oct 27, 2021
@aloisdg
Copy link
Contributor

aloisdg commented Oct 27, 2021

Here I would say what make sense for the fr culture:

    [UseCulture("fr-FR")]
    [Theory]
    [InlineData(00, 00, "minuit")]
    [InlineData(04, 00, "quatre heure")]
    [InlineData(05, 01, "cinq heure un")]
    [InlineData(06, 05, "six heure cinq")]
    [InlineData(07, 10, "sept heure dix")]
    [InlineData(08, 15, "huit heure et quart")]
    [InlineData(09, 20, "neuf heure vingt")]
    [InlineData(10, 25, "dix heure vingt-cinq")]
    [InlineData(11, 30, "onze heure et demi")]
    [InlineData(12, 00, "midi")]
    [InlineData(15, 35, "quainze heure trentre-cinq")]
    [InlineData(16, 40, "cinq heure moins vingt")]
    [InlineData(17, 45, "six heure moins le quart")]
    [InlineData(18, 50, "sept heure moins dix")]
    [InlineData(19, 55, "huit heure moins cinq")]
    [InlineData(20, 59, "vingt heure cinquante-neuf")]
    public void ConvertToClockNotationTimeOnlyStringEnUs(int hours, int minutes, string expectedResult)
    {
        var actualResult = new TimeOnly(hours, minutes).ToClockNotation();
        Assert.Equal(expectedResult, actualResult);
    }

and for the code it would look like (not tested yet):

public class DefaultTimeOnlyToClockNotationConverter : ITimeOnlyToClockNotationConverter
{
    public virtual string Convert(TimeOnly time)
    {
        switch (time)
        {
            case { Hour: 0, Minute: 0 }:
                return "minuit";
            case { Hour: 12, Minute: 0 }:
                return "midi";
        }

        var normalizedHour = time.Hour % 12;

        return time switch
        {
            { Minute: 00 } => $"{normalizedHour.ToWords()} pile",
            { Minute: 05 } => $"{normalizedHour.ToWords()} heure cinq", // should not be needed
            { Minute: 10 } => $"{normalizedHour.ToWords()} heure dix", // should not be needed
            { Minute: 15 } => $"{normalizedHour.ToWords()} et quart",
            { Minute: 20 } => $"{normalizedHour.ToWords()} heure vingt", // should not be needed
            { Minute: 25 } => $"{normalizedHour.ToWords()} heure vingt-cinq", // should not be needed
            { Minute: 30 } => $"{normalizedHour.ToWords()} et demi",
            { Minute: 40 } => $"{(normalizedHour + 1).ToWords() moins vingt}",
            { Minute: 45 } => $"{(normalizedHour + 1).ToWords() moins le quart}",
            { Minute: 50 } => $"{(normalizedHour + 1).ToWords() moins dix}",
            { Minute: 55 } => $"{(normalizedHour + 1).ToWords() moins cinq}",
            _ => $"{normalizedHour.ToWords()} {time.Minute.ToWords()}"
        };
    }
}

If this PR is merged I will create a new one to add the above french translation

@clairernovotny
Copy link
Member

clairernovotny commented Oct 27, 2021

@hangy when you think this is ready, pls approve the PR and I'll merge.

@strobelt Thanks for this contribution!

@strobelt
Copy link
Contributor Author

Thanks for all the @hangy ! I removed the de locale from the registry and made the new converters internal. Let me know if you find any more issues with the PR

@hazzik
Copy link
Member

hazzik commented Oct 28, 2021

What happens to unregistered locale?

@hangy
Copy link
Contributor

hangy commented Oct 28, 2021

What happens to unregistered locale?

de? Needs to be implemented separately. In fact, I have a draft for that locally.

@hazzik
Copy link
Member

hazzik commented Oct 28, 2021

No, I mean what happens if someone calls this method with locale for which formatter is not registered? Exception?

@hangy
Copy link
Contributor

hangy commented Oct 28, 2021

No, I mean what happens if someone calls this method with locale for which formatter is not registered? Exception?

Unless I'm missing something, it would behave like most Humanizer methods, and fall back to English.

@clairernovotny
Copy link
Member

Looking at the way the translations work, either on the 5's or quarters, do we think we should add a rounding option so that a time rounds up to the nearest group? So 03:44 would still read as quarter to four given that it's close enough?

@strobelt
Copy link
Contributor Author

@hazzik I run the tests with different cultures and it uses the default one, as @hangy said.

@clairernovotny Nice catch there with the registry! I removed them and the tests worked as expected.

Also, I think a rounding option is really interesting. I thought about having some RoundingOption enum, or RoundToNearestFive boolean as an optional param to the Converter so that by default it converts to the minute and if provided, it rounds as suggested. What do you think?

And I thought about adding rounding by the previous two minutes and the next two minutes, so anything between 03:43 and 03:47 would become a quarter to four. How about this?

            return time switch
            {
                { Minute: < 05 } => $"{normalizedHour.ToWords()} o'clock",
                { Minute: <= 07 } => $"five past {normalizedHour.ToWords()}",
                { Minute: <= 12 } => $"ten past {normalizedHour.ToWords()}",
                { Minute: <= 17 } => $"a quarter past {normalizedHour.ToWords()}",
                { Minute: <= 22 } => $"twenty past {normalizedHour.ToWords()}",
                { Minute: <= 27 } => $"twenty-five past {normalizedHour.ToWords()}",
                { Minute: <= 32 } => $"half past {normalizedHour.ToWords()}",
                { Minute: <= 37 } => $"twenty-five to {(normalizedHour + 1).ToWords()}",
                { Minute: <= 42 } => $"twenty to {(normalizedHour + 1).ToWords()}",
                { Minute: <= 47 } => $"a quarter to {(normalizedHour + 1).ToWords()}",
                { Minute: <= 52 } => $"ten to {(normalizedHour + 1).ToWords()}",
                { Minute: <= 57 } => $"five to {(normalizedHour + 1).ToWords()}",
                { Minute: > 57 } => $"{(normalizedHour + 1).ToWords()} o'clock"
            };

@clairernovotny
Copy link
Member

clairernovotny commented Oct 29, 2021

@strobelt sounds good to me! Gotta love switch expressions :)

@strobelt
Copy link
Contributor Author

Great! I made the change and went with the enum to make it more clear when using the converter.
What do you think?

@clairernovotny
Copy link
Member

Ping @strobelt? I'd love to get a new release out that includes these changes!

@strobelt
Copy link
Contributor Author

Hi @clairernovotny! I just updated the PR. Thanks for the heads-up with this

@clairernovotny clairernovotny merged commit 18167e5 into Humanizr:main Nov 11, 2021
@strobelt strobelt deleted the clocktime-timeonly-strategy branch November 11, 2021 20:09
@neilboyd neilboyd mentioned this pull request Feb 8, 2022
11 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants