Skip to content

Commit d7fcc92

Browse files
authored
Merge pull request #4 from mamyraoby/develop
Develop
2 parents bcb0fac + 14a6186 commit d7fcc92

File tree

12 files changed

+447
-22
lines changed

12 files changed

+447
-22
lines changed

composer.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "mamyraoby/phanisana",
3-
"description": "Phanisana is a powerful PHP library that transforms numbers, dates, and times into their full-text equivalents in the Malagasy 🇲🇬 language. Whether you're localizing applications, building educational tools, or creating systems with Malagasy users in mind, Phanisana offers a reliable and intuitive solution",
3+
"description": "Phanisana is a powerful PHP library that transforms numbers, dates, and times into their full-text equivalents in the Malagasy 🇲🇬 language.",
44
"license": "GPL-3.0-only",
55
"type": "library",
66
"autoload": {
@@ -26,6 +26,7 @@
2626
"friendsofphp/php-cs-fixer": "^3.75"
2727
},
2828
"scripts": {
29-
"test": "phpunit"
29+
"test": "phpunit",
30+
"fix": "php-cs-fixer fix"
3031
}
3132
}

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ The name **Phanisana** is a creative fusion of **PHP**, the programming language
1212
- Millions (*tapitrisa*)
1313
- Billions (*lavitrisa*)
1414
- Trillions (*arivo lavitrisa*) and beyond.
15-
- Easy-to-use API with clean and expressive syntax.
15+
- Easy-to-use library with clean and expressive syntax.
1616
- Designed with extensibility in mind for future date/time support.
1717

1818
## 🚀 Getting Started

docs/installation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,4 @@ require 'vendor/autoload.php';
3838

3939
You’re now ready to start using **Phanisana**! 🎉
4040

41-
For usage examples and advanced configuration, check out the [Usage Guide](./usage.md).
41+
For usage examples and advanced configuration, check out the [Usage Guide](./usages/number).

docs/usages/datetime.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
2+
3+
# Date and Time Conversion
4+
5+
**Phanisana** provides robust support for converting PHP `DateTime` objects or strings into their full **Malagasy word representations**, suitable for natural language outputs, localization, or educational tools.
6+
7+
---
8+
9+
## 📅 Date Conversion
10+
11+
The `DateTimeConverter` class provides multiple formatting levels for converting dates into readable Malagasy text.
12+
13+
### ✅ Available Date Formats
14+
15+
| Constant | Description | Example Output |
16+
|----------------------------------|----------------------------------------|---------------------------------------------------------------------------|
17+
| `FORMAT_DATE_LONG_TEXT` | Full text with day of the week | `alakamisy fito amby folo aprily taona dimy amby roapolo sy roa arivo` |
18+
| `FORMAT_DATE_MEDIUM_TEXT` | Date with month and year | `fito amby folo aprily taona dimy amby roapolo sy roa arivo` |
19+
| `FORMAT_DATE_TEXT` | Shorter form without “taona” | `fito amby folo aprily dimy amby roapolo sy roa arivo` |
20+
21+
You can also use custom PHP-style date format strings:
22+
23+
```php
24+
echo $converter->convertDate($date, 'd F Y');
25+
// Output: 17 Aprily 2025
26+
27+
echo $converter->convertDate($date, 'l d F Y');
28+
// Output: Alakamisy 17 Aprily 2025
29+
30+
echo $converter->convertDate($date, 'D d F Y');
31+
// Output: Alak 17 Aprily 2025
32+
```
33+
34+
---
35+
36+
## 🕰️ Time Conversion
37+
38+
Time strings can be converted to natural Malagasy expressions. Input should be in `HH:mm` or `HH:mm:ss` format (24-hour clock).
39+
40+
### ✅ Examples
41+
42+
```php
43+
echo $converter->convertTime('08:10', DateTimeConverter::FORMAT_TIME_LONG_TEXT);
44+
// Output: valo ora maraina sy folo minitra
45+
46+
echo $converter->convertTime('00:00', DateTimeConverter::FORMAT_TIME_LONG_TEXT);
47+
// Output: roa amby folo ora alina
48+
49+
echo $converter->convertTime('12:00', DateTimeConverter::FORMAT_TIME_LONG_TEXT);
50+
// Output: roa amby folo ora atoandro
51+
52+
echo $converter->convertTime('13:39', DateTimeConverter::FORMAT_TIME_LONG_TEXT);
53+
// Output: iray ora atoandro sy sivy amby telopolo minitra
54+
55+
echo $converter->convertTime('18:14:56', DateTimeConverter::FORMAT_TIME_LONG_TEXT);
56+
// Output: enina ora hariva sy efatra amby folo minitra sy enina amby dimampolo segondra
57+
```
58+
59+
> ⚠️ **Note:** Invalid time format (e.g. `08:90`) will throw an `InvalidTimeFormatException`.
60+
61+
---
62+
63+
## 🌅 Malagasy Day Parts
64+
65+
Depending on the time of day, Malagasy uses different words to indicate the period:
66+
67+
| Malagasy | English |
68+
|----------------|------------------|
69+
| **maraina** | morning |
70+
| **atoandro** | noon / midday |
71+
| **tolakandro** | afternoon |
72+
| **hariva** | evening |
73+
| **alina** | night |
74+
75+
---
76+
77+
## 📆 Full DateTime Conversion
78+
79+
Combine both `convertDate` and `convertTime` to represent a full Malagasy datetime expression.
80+
81+
```php
82+
$date = new DateTime('2025-04-17 18:14:56');
83+
84+
echo $converter->convertDate($date, DateTimeConverter::FORMAT_DATE_LONG_TEXT);
85+
// Output: alakamisy fito amby folo aprily taona dimy amby roapolo sy roa arivo
86+
87+
echo $converter->convertTime($date->format('H:i:s'), DateTimeConverter::FORMAT_TIME_LONG_TEXT);
88+
// Output: enina ora hariva sy efatra amby folo minitra sy enina amby dimampolo segondra
89+
```
90+
91+
---
92+
93+
## 🎁 Bonus Feature: Extended DateTime Class
94+
95+
Phanisana also includes a convenient `DateTime` class that extends PHP’s native `DateTime` class. This extended class provides all standard datetime manipulation capabilities while integrating full support for **Malagasy localization**.
96+
97+
You can use it anywhere in your application to seamlessly manipulate and output Malagasy-friendly date and time strings.
98+
99+
```php
100+
use Phanisana\DateTime;
101+
102+
$datetime = new DateTime('2025-04-17 18:14:56');
103+
104+
echo $datetime->format('l d F Y');
105+
// Output: Alakamisy 17 Aprily 2025
106+
```
107+
108+
> 📝 **Note:** All regular PHP `DateTime` format strings are fully supported.
109+
110+
### 📖 Common PHP DateTime Format Characters
111+
112+
| Format | Description | Example Output |
113+
|--------|-------------------------|-----------------------|
114+
| `Y` | Full numeric year | `2025` |
115+
| `y` | Two-digit year | `25` |
116+
| `m` | Numeric month (01–12) | `04` |
117+
| `F` | Full month name | `Aprily` |
118+
| `d` | Day of the month | `17` |
119+
| `l` | Full weekday name | `Alakamisy` |
120+
| `D` | Short weekday name | `Alak` |
121+
| `H` | Hour (00–23) | `18` |
122+
| `i` | Minutes (00–59) | `14` |
123+
| `s` | Seconds (00–59) | `56` |
124+
125+
These can be combined to format output however you need:
126+
```php
127+
echo $datetime->format('l d F Y H:i:s');
128+
// Output: Alakamisy 17 Aprily 2025 18:14:56
129+
```
130+

helpers/converter.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
use MamyRaoby\Phanisana\Converter\NumberConverter;
44

55
if (!function_exists('phanisana_convert_number')) {
6-
function phanisana_convert_number(int $number): string {
6+
function phanisana_convert_number(int $number): string
7+
{
78
$converter = new NumberConverter();
89
return $converter->toWords($number);
910
}
10-
}
11+
}
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
<?php
2+
3+
namespace MamyRaoby\Phanisana\Converter;
4+
5+
use DateTimeInterface;
6+
use MamyRaoby\Phanisana\Enum\Dictionary;
7+
use MamyRaoby\Phanisana\Enum\DateDictionary;
8+
use MamyRaoby\Phanisana\Exceptions\InvalidTimeFormatException;
9+
use MamyRaoby\Phanisana\Extensions\DateTime;
10+
11+
class DateTimeConverter
12+
{
13+
public const FORMAT_DATE_LONG_TEXT = 'DATE_LONG_TEXT';
14+
public const FORMAT_DATE_MEDIUM_TEXT = 'DATE_MEDIUM_TEXT';
15+
public const FORMAT_DATE_TEXT = 'DATE_TEXT';
16+
public const FORMAT_TIME_LONG_TEXT = 'TIME_LONG_TEXT';
17+
18+
public function convertDate(DateTimeInterface $date, string $format): string
19+
{
20+
$localeDate = DateTime::createFromInterface($date);
21+
22+
if ($format === self::FORMAT_DATE_LONG_TEXT) {
23+
$weekday = $localeDate->format('l');
24+
$day = phanisana_convert_number($localeDate->format('d'));
25+
$month = $localeDate->format('F');
26+
$year = phanisana_convert_number($localeDate->format('Y'));
27+
28+
return strtolower(implode(' ', [$weekday, $day, $month, DateDictionary::YEAR->value, $year]));
29+
}
30+
31+
if ($format === self::FORMAT_DATE_MEDIUM_TEXT) {
32+
$day = phanisana_convert_number($localeDate->format('d'));
33+
$month = $localeDate->format('F');
34+
$year = phanisana_convert_number($localeDate->format('Y'));
35+
36+
return strtolower(implode(' ', [$day, $month, DateDictionary::YEAR->value, $year]));
37+
}
38+
39+
if ($format === self::FORMAT_DATE_TEXT) {
40+
$day = phanisana_convert_number($localeDate->format('d'));
41+
$month = $localeDate->format('F');
42+
$year = phanisana_convert_number($localeDate->format('Y'));
43+
44+
return strtolower(implode(' ', [$day, $month, $year]));
45+
}
46+
47+
return $localeDate->format($format);
48+
}
49+
50+
/**
51+
* @throws \MamyRaoby\Phanisana\Exceptions\InvalidTimeFormatException
52+
*/
53+
public function convertTime(DateTimeInterface|string $time, string $format): string
54+
{
55+
if ($time instanceof DateTimeInterface) {
56+
$time = $time->format('H:i:s');
57+
}
58+
59+
$pattern = '/^([01]\d|2[0-3]):([0-5]\d)(:[0-5]\d)?$/';
60+
61+
$isValid = preg_match($pattern, $time) === 1;
62+
63+
if ($isValid) {
64+
$splittedTime = explode(':', $time);
65+
$hour = intval($splittedTime[0]);
66+
$minute = intval($splittedTime[1]);
67+
$second = intval($splittedTime[2] ?? 0);
68+
69+
$hourSuffix = $this->getHourSuffix($hour);
70+
71+
if ($hour > 12) {
72+
$hour = $hour - 12;
73+
}
74+
75+
if ($hour === 0) {
76+
$hour = 12;
77+
}
78+
79+
$words = [];
80+
81+
$words[] = phanisana_convert_number($hour);
82+
$words[] = DateDictionary::HOUR->value;
83+
$words[] = $hourSuffix;
84+
85+
if ($minute > 0) {
86+
$words[] = Dictionary::GLUE_SY->value;
87+
$words[] = phanisana_convert_number($minute);
88+
$words[] = DateDictionary::MINUTE->value;
89+
}
90+
91+
if ($second > 0) {
92+
$words[] = Dictionary::GLUE_SY->value;
93+
$words[] = phanisana_convert_number($second);
94+
$words[] = DateDictionary::SECOND->value;
95+
}
96+
97+
return trim(preg_replace('/\s+/', ' ', implode(' ', $words)));
98+
}
99+
100+
throw new InvalidTimeFormatException();
101+
}
102+
103+
public function convertDateTime(DateTimeInterface $datetime, string $dateFormat, string $timeFormat): string
104+
{
105+
$date = $this->convertDate($datetime, $dateFormat);
106+
$time = $this->convertTime($datetime, $timeFormat);
107+
108+
return $date . DateDictionary::GLUE_AT->value . $time;
109+
}
110+
111+
protected function getHourSuffix(int $hour): string
112+
{
113+
$suffix = '';
114+
115+
if (in_array($hour, range(1, 10))) {
116+
$suffix = DateDictionary::MORNING->value;
117+
}
118+
119+
if (in_array($hour, range(11, 13))) {
120+
$suffix = DateDictionary::MIDDAY->value;
121+
}
122+
123+
if (in_array($hour, range(14, 16))) {
124+
$suffix = DateDictionary::AFTERNOON->value;
125+
}
126+
127+
if (in_array($hour, range(17, 23))) {
128+
$suffix = DateDictionary::EVENING->value;
129+
}
130+
131+
if ($hour === 0) {
132+
$suffix = DateDictionary::NIGHT->value;
133+
}
134+
135+
return $suffix;
136+
}
137+
}

src/Converter/NumberConverter.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,63 +8,63 @@ class NumberConverter
88
{
99
public function toWords(int $number): string
1010
{
11-
if ($result = $this->convertWithThreshold($number, 1_000_000_000_000_000_000, fn($n) => $this->convertQuintillions($n))) {
11+
if ($result = $this->convertWithThreshold($number, 1_000_000_000_000_000_000, fn ($n) => $this->convertQuintillions($n))) {
1212
return $result;
1313
}
1414

15-
if ($result = $this->convertWithThreshold($number, 100_000_000_000_000_000, fn($n) => $this->convertHundredQuadrillions($n))) {
15+
if ($result = $this->convertWithThreshold($number, 100_000_000_000_000_000, fn ($n) => $this->convertHundredQuadrillions($n))) {
1616
return $result;
1717
}
1818

19-
if ($result = $this->convertWithThreshold($number, 10_000_000_000_000_000, fn($n) => $this->convertTenQuadrillions($n))) {
19+
if ($result = $this->convertWithThreshold($number, 10_000_000_000_000_000, fn ($n) => $this->convertTenQuadrillions($n))) {
2020
return $result;
2121
}
2222

23-
if ($result = $this->convertWithThreshold($number, 1_000_000_000_000_000, fn($n) => $this->convertQuadrillions($n))) {
23+
if ($result = $this->convertWithThreshold($number, 1_000_000_000_000_000, fn ($n) => $this->convertQuadrillions($n))) {
2424
return $result;
2525
}
2626

27-
if ($result = $this->convertWithThreshold($number, 100_000_000_000_000, fn($n) => $this->convertHundredTrillions($n))) {
27+
if ($result = $this->convertWithThreshold($number, 100_000_000_000_000, fn ($n) => $this->convertHundredTrillions($n))) {
2828
return $result;
2929
}
3030

31-
if ($result = $this->convertWithThreshold($number, 10_000_000_000_000, fn($n) => $this->convertTenTrillions($n))) {
31+
if ($result = $this->convertWithThreshold($number, 10_000_000_000_000, fn ($n) => $this->convertTenTrillions($n))) {
3232
return $result;
3333
}
3434

35-
if ($result = $this->convertWithThreshold($number, 1_000_000_000_000, fn($n) => $this->convertTrillions($n))) {
35+
if ($result = $this->convertWithThreshold($number, 1_000_000_000_000, fn ($n) => $this->convertTrillions($n))) {
3636
return $result;
3737
}
3838

39-
if ($result = $this->convertWithThreshold($number, 100_000_000_000, fn($n) => $this->convertHundredBillions($n))) {
39+
if ($result = $this->convertWithThreshold($number, 100_000_000_000, fn ($n) => $this->convertHundredBillions($n))) {
4040
return $result;
4141
}
4242

43-
if ($result = $this->convertWithThreshold($number, 10_000_000_000, fn($n) => $this->convertTenBillions($n))) {
43+
if ($result = $this->convertWithThreshold($number, 10_000_000_000, fn ($n) => $this->convertTenBillions($n))) {
4444
return $result;
4545
}
4646

47-
if ($result = $this->convertWithThreshold($number, 1_000_000_000, fn($n) => $this->convertBillions($n))) {
47+
if ($result = $this->convertWithThreshold($number, 1_000_000_000, fn ($n) => $this->convertBillions($n))) {
4848
return $result;
4949
}
5050

51-
if ($result = $this->convertWithThreshold($number, 100_000_000, fn($n) => $this->convertHundredMillions($n))) {
51+
if ($result = $this->convertWithThreshold($number, 100_000_000, fn ($n) => $this->convertHundredMillions($n))) {
5252
return $result;
5353
}
5454

55-
if ($result = $this->convertWithThreshold($number, 10_000_000, fn($n) => $this->convertTenMillions($n))) {
55+
if ($result = $this->convertWithThreshold($number, 10_000_000, fn ($n) => $this->convertTenMillions($n))) {
5656
return $result;
5757
}
5858

59-
if ($result = $this->convertWithThreshold($number, 1_000_000, fn($n) => $this->convertMillions($n))) {
59+
if ($result = $this->convertWithThreshold($number, 1_000_000, fn ($n) => $this->convertMillions($n))) {
6060
return $result;
6161
}
6262

63-
if ($result = $this->convertWithThreshold($number, 100_000, fn($n) => $this->convertHundredThousands($n))) {
63+
if ($result = $this->convertWithThreshold($number, 100_000, fn ($n) => $this->convertHundredThousands($n))) {
6464
return $result;
6565
}
6666

67-
if ($result = $this->convertWithThreshold($number, 10_000, fn($n) => $this->convertTenThousands($n))) {
67+
if ($result = $this->convertWithThreshold($number, 10_000, fn ($n) => $this->convertTenThousands($n))) {
6868
return $result;
6969
}
7070

0 commit comments

Comments
 (0)