briannesbitt / Carbon

A simple PHP API extension for DateTime.
https://carbon.nesbot.com/
MIT License
16.52k stars 1.28k forks source link

Format "2024-02-29" to Buddhist Era so to obtain "2567-02-29" #2954

Open mean-cj opened 6 months ago

mean-cj commented 6 months ago

Hello,

Convert year "2024-02-29" to Buddhist Era, the result month is incorrect "2567-03-01" https://play.phpsandbox.io/nesbot/carbon/wlJnA5ObQjKQBX89

Input AD (Anno Domini): 2024-02-29
Output BE (Buddhist Era): 2567-03-01

I encountered an issue with the following code:

use Carbon\Carbon;

// Assuming $gregorianYear is the Gregorian calendar year 
$gregorianYear = "2024-02-29";

// Convert to Buddhist calendar year
$buddhistYear = Carbon::createFromDate($gregorianYear)->addYears(543)->format("Y-m-d");

echo "AD (Anno Domini): $gregorianYear\n";
echo "BE (Buddhist Era): $buddhistYear\n";

Carbon version: 2.72.2 , 3.0.2 PHP version: 7.4.33, 8.2.15

I expected to get: "2024-02-29" -> "2567-02-29"

Thanks!

kylekatarnls commented 6 months ago

Hello,

I'm sorry for that but there is no way to support that.

First, there are legit cases where people doing Carbon::createFromDate($gregorianYear)->addYears($number) want to add $number years and expect to obtain a Gregorian date. And so it has to be a valid Gregorian date (either Feb 28th without overflow, or March 1st with overflow), but not February 29th because it does not exist in Gregorian year 2567.

Even if we would like to support that Carbon would not be able to as it's based on native PHP DateTime object which is always representing a single point in time with the constraint to be a valid Gregorian and valid in the given timezone.

You're recommended to split units not to handle Buddhist Era values using a Carbon or DateTime object:

use Carbon\Carbon;

$gregorianDate = '2024-02-29';
$carbonObject = Carbon::createFromDate($gregorianDate);

$buddhistDate = $carbonObject->addYears(543)->year . $carbonObject->format('-m-d');

echo "AD (Anno Domini): $gregorianDate\n";
echo "BE (Buddhist Era): $buddhistDate\n";

Thanks.

mean-cj commented 6 months ago

Hi @kylekatarnls

Thank you for your answer.

What do you think ?

Add my method "formatBuddhist()" for Buddhist Era year format nesbot/carbon/src/Carbon/Traits/Converter.php

    public function formatBuddhist(string $format): string
    {

        if (strpos($format, 'o') !== false) {
            $format = str_replace('o', '[{1}]', $format);
        }

        if (strpos($format, 'Y') !== false) {
            $format = str_replace('Y', '[{2}]', $format);
        }

        if (strpos($format, 'y') !== false) {
            $format = str_replace('y', '[{3}]', $format);
        }

        $function = $this->localFormatFunction ?: static::$formatFunction;

        if (!$function) {
            $format = $this->rawFormat($format);
        }
        else if (\is_string($function) && method_exists($this, $function)) {
            $format = [$this, $function];
            $format = $function(...\func_get_args());
        }

        $timestamp = $this->getTimestamp();

        if (strpos($format, '[{1}]') !== false) {
            $format = str_replace('[{1}]', (date('o', $timestamp) + 543), $format);
        }

        if (strpos($format, '[{2}]') !== false) {
            $format = str_replace('[{2}]', (date('Y', $timestamp) + 543), $format);
        }

        if (strpos($format, '[{3}]') !== false) {
            $year = (date('y', $timestamp) + 43) % 100;
            $format = str_replace('[{3}]', $year, $format);
        }

        return $format;
    }
    $parse = Carbon::parse('2024-02-29 10.55.32');
    echo $parse->formatBuddhist("Y-m-d H:i:s"); // 2567-02-29 10:55:32
    echo $parse->formatBuddhist("d/m/y H:i:s"); // 29/02/67 10:55:32

If you agree, please add both version 2.x , 3.x versions.

kylekatarnls commented 6 months ago

Hello,

I'm not sure yet if this should be included into Carbon itself, go into a separate package, or simply be documented as a macro.

I also see few little possible code changes, for instance, it's not useful to step by the timestamp and involve date() such as in date('o', $timestamp) you can simply do $this->format('o'). You can also use the method str_contains (even if you don't have PHP 8 yet as nesbot/carbon includes the symfony polyfill for it). And maybe all the if are not even worth it because if you call $format = str_replace('o', '[{1}]', $format); and that the string does not contain o it will just do nothing.

Be careful of $year = (date('y', $timestamp) + 43) % 100;, when it's < 10, you won't have a trailing zero. You'd get 29/02/7 while this format is supposed to give: 29/02/07, str_pad can be used to add the trailing 0.

Unfortunately, 2.x is now only receiving bugfixes. New features only comes into 3.x. On the other side, using macro, it would work on any object v2 or v3:

Carbon::macro('formatBuddhist', static function (string $format): string {
    $self = self::this();

    $format = strtr($format, [
        'o' => '[{1}]',
        'Y' => '[{2}]',
        'y' => '[{3}]',
    ]);

    $function = $self->localFormatFunction ?: static::$formatFunction;

    if (!$function) {
        $format = $self->rawFormat($format);
    } else if (\is_string($function) && method_exists($self, $function)) {
        $format = [$self, $function];
        $format = $function(...\func_get_args());
    }

    $buddhistYear = $self->year + 543;

    return strtr($format, [
        '[{1}]' => $self->format('o') + 543,
        '[{2}]' => $buddhistYear,
        '[{3}]' => str_pad($buddhistYear % 100, 2, '0', STR_PAD_LEFT),
    ]);
});
mean-cj commented 6 months ago

Hello

Thank you so much for everything, suggestion and for your "Carbon" i love it.

The Buddhist calendar is a set of lunisolar calendars primarily used in Tibet, Cambodia, Laos, Myanmar, India, Sri Lanka, Thailand and Vietnam as well as in Malaysia and Singapore and by Chinese populations for religious or official occasions.

https://en.wikipedia.org/wiki/Buddhist_calendar

Actually, I think adding to carbon is also possible. Just a few lines more, or we can separate it into a separate package.

Have a good day, Thanks