A comprehensive PHP library for converting between Gregorian and Khmer (Cambodian) calendar dates. This is a faithful port of the popular JavaScript momentkh library by ThyrithSor, enhanced with modern PHP best practices and comprehensive testing.
- β Gregorian to Khmer Lunar Date Conversion - Full bidirectional date conversion with lunar calendar support
- β Buddhist Era (BE) Year Support - Accurate BE year calculations and conversion utilities
- β Khmer New Year Calculations - Precise calculation of Khmer New Year moments using advanced algorithms
- β Animal Year System - Complete 12-year animal cycle (ααααΆααααα) support
- β Era Year System - 10-year era cycle (ααα) calculations
- β Leap Year Support - Accurate lunar calendar leap year handling
- β Flexible Date Formatting - Customizable output with Khmer formatting tokens
- β Number Conversion - Bidirectional Arabic β Khmer numeral conversion
- β Khmer Text Support - Full Unicode Khmer character support and validation
- β Multiple Calendar Systems - Solar and lunar month name support
- β Modern PHP (7.4+) - Strict types, comprehensive type hints, and modern syntax
- β Comprehensive Testing - Full PHPUnit test suite with edge case coverage
- β Static Analysis - PHPStan ready with high code quality standards
- β PSR-12 Standards - Follows PHP coding standards and best practices
- β Rich Documentation - Detailed PHPDoc annotations and usage examples
- PHP 7.4 or higher
- ext-json (usually included)
Install via Composer:
composer require pphatdev/lunar-date
<?php
require_once 'vendor/autoload.php';
use PPhatDev\LunarDate\KhmerDate;
// Create Khmer date from current time
$today = new KhmerDate();
echo $today->toLunarDate();
// Output: ααααα’αΆαα·ααα α¨ααΎα αααα·ααα·α ααααΆαααΌα ααααΉαααα·ααα αα»αααααααΆα α’α₯α¦α§
// Convert specific Gregorian date
$date = new KhmerDate('1996-09-24');
echo $date->toLunarDate();
// Output: ααααα’ααααΆα α‘α’ααΎα ααα’αααα»α ααααΆαα
ααααΉαααα·ααα αα»αααααααΆα α’α₯α£α©
// Custom formatting
echo $date->toLunarDate('dN ααααW ααm α.α. b');
// Output: α‘α’ααΎα ααααα’ααααΆα ααα’αααα»α α.α. α’α₯α£α©
// Khmer Gregorian date
echo $date->toKhmerDate();
// Output: α’α€ αααααααΆ ααααΆαα‘α©α©α¦
The main class for working with Khmer calendar dates.
// Current date and time
$now = new KhmerDate();
// From DateTime object
$dateTime = new DateTime('2024-01-15');
$khmerDate = new KhmerDate($dateTime);
// From string (various formats supported)
$khmerDate = new KhmerDate('2024-01-15');
$khmerDate = new KhmerDate('2024-01-15 14:30:00');
// From timestamp
$khmerDate = new KhmerDate(1705315800);
// Static factory methods
$khmerDate = KhmerDate::create('2024-01-15');
$khmerDate = KhmerDate::createFromDateTime($dateTime);
$date = new KhmerDate('2024-01-15');
// Lunar date conversion
echo $date->toLunarDate(); // Full Khmer lunar date
echo $date->toLunarDate('dN ααm α.α. b'); // Custom format
// Khmer Gregorian date
echo $date->toKhmerDate(); // Khmer numerals with Gregorian calendar
// Calendar components
echo $date->khDay(); // Lunar day (0-29): 8
echo $date->khMonth(); // Lunar month index: 2
echo $date->khYear(); // Buddhist Era year: 2567
// Standard DateTime operations
echo $date->format('Y-m-d H:i:s'); // Standard formatting
echo $date->getTimestamp(); // Unix timestamp
$copy = $date->copy(); // Create copy
$date = new KhmerDate('2024-01-15');
// Add/subtract time intervals
$futureDate = $date->add('P1M'); // Add 1 month
$pastDate = $date->subtract('P7D'); // Subtract 7 days
// The original date object is modified, use copy() if needed
$newDate = $date->copy()->add('P1Y'); // Add 1 year to copy
$date = new KhmerDate('2024-01-15');
// Lunar date conversion
echo $date->toLunarDate(); // Full Khmer lunar date
echo $date->toLunarDate('dN ααm α.α. b'); // Custom format
// Khmer Gregorian date
echo $date->toKhmerDate(); // Khmer numerals with Gregorian calendar
// Calendar components
echo $date->khDay(); // Lunar day (0-29): 8
echo $date->khMonth(); // Lunar month index: 2
echo $date->khYear(); // Buddhist Era year: 2567
// Standard DateTime operations
echo $date->format('Y-m-d H:i:s'); // Standard formatting
echo $date->getTimestamp(); // Unix timestamp
$copy = $date->copy(); // Create copy
$date = new KhmerDate('2024-01-15');
// Add/subtract time intervals
$futureDate = $date->add('P1M'); // Add 1 month
$pastDate = $date->subtract('P7D'); // Subtract 7 days
// The original date object is modified, use copy() if needed
$newDate = $date->copy()->add('P1Y'); // Add 1 year to copy
// Get exact Khmer New Year moment for any year
$newYear2024 = KhmerDate::getKhNewYearMoment(2024);
echo $newYear2024->format('Y-m-d H:i:s'); // 2024-04-14 06:46:00
// Calculate for multiple years
foreach (range(2020, 2030) as $year) {
$newYear = KhmerDate::getKhNewYearMoment($year);
echo "Khmer New Year $year: " . $newYear->format('Y-m-d H:i') . "\n";
}
// Arabic to Khmer numerals
echo KhmerDate::arabicToKhmerNumber('2024'); // α’α α’α€
echo KhmerDate::arabicToKhmerNumber('15'); // α‘α₯
// Khmer to Arabic numerals
echo KhmerDate::khmerToArabicNumber('α’α α’α€'); // 2024
echo KhmerDate::khmerToArabicNumber('α‘α₯'); // 15
// Helper method
echo KhmerDate::getKhmerNumber(2024); // α’α α’α€
// Get month names
$lunarMonths = KhmerDate::getKhmerMonthNames();
// ['αα·ααα·α', 'αα»ααα', 'ααΆα', 'ααααα»α', 'α
αααα', 'αα·ααΆα', 'ααααα', 'α’αΆααΆα', 'ααααΆααα', 'αααααα', 'α’αααα»α', 'ααααα·α', 'ααααΆααΆα', 'αα»αα·ααΆααΆα']
// Get animal year names (12-year cycle)
$animalYears = KhmerDate::getAnimalYearNames();
// ['ααΌα', 'ααααΌα', 'ααΆα', 'ααα', 'ααα', 'ααααΆαα', 'αααΈ', 'ααα', 'αα', 'αααΆ', 'α
', 'αα»α']
// Get era year names (10-year cycle)
$eraYears = KhmerDate::getEraYearNames();
// ['ααααΉαααα·ααα', 'α―αααα', 'ααααα', 'ααααΈααα', 'α
ααααΆααα', 'αααα
ααα', 'αααα', 'ααααααα', 'α’αααααα', 'ααααααα']
use PPhatDev\LunarDate\Utils;
// Parse Khmer date string
$parsed = Utils::parseKhmerDate('α‘α’ααΎα ααα’αααα»α α.α. α’α₯α¦α§');
var_dump($parsed); // Returns parsed date components
// Get Khmer month date range
$monthRange = Utils::getKhmerMonthRange(5, 2567); // αα·ααΆα month in BE 2567
foreach ($monthRange as $day) {
echo "Day {$day['day']}: {$day['lunar']} - {$day['gregorian']}\n";
}
// Find specific lunar days in a year
$fullMoons = Utils::findLunarDayOccurrences(15, 0, 2024); // All 15ααΎα in 2024
$newMoons = Utils::findLunarDayOccurrences(15, 1, 2024); // All 15ααα
in 2024
// Date difference in Khmer terms
$date1 = new KhmerDate('2024-01-01');
$date2 = new KhmerDate('2024-12-31');
$diff = Utils::diffInKhmer($date1, $date2);
echo "Difference: {$diff['days']} days, {$diff['months']} months";
// Buddhist holidays for a specific year
$holidays = Utils::getBuddhistHolidays(2024);
foreach ($holidays as $holiday) {
echo "{$holiday['name']}: {$holiday['date']}\n";
}
// Era conversion utilities
$beYear = Utils::convertEra(2024, 'AD', 'BE'); // 2567
$adYear = Utils::convertEra(2567, 'BE', 'AD'); // 2024
$jsYear = Utils::convertEra(2024, 'AD', 'JS'); // 1385
// Date validation
$isValid = Utils::isValidKhmerDate(15, 5, 2567); // true
$isValid = Utils::isValidKhmerDate(31, 5, 2567); // false (invalid lunar day)
// Season information based on lunar calendar
$date = new KhmerDate('2024-07-15');
$season = Utils::getSeason($date);
echo $season['name']; // αααΌααααααΆ (Rainy Season)
echo $season['name_en']; // Rainy Season
The library supports extensive formatting tokens inspired by momentkh:
Token | Description | Example Output |
---|---|---|
W |
αααααααααααΆα α (Day of week full) | α’ααααΆα |
w |
αααααααααααΆα αααΆαα (Day of week short) | α’ |
d |
ααααααΈ (Lunar day count) | α‘α’ |
D |
ααααααΈ α α‘-α‘α₯ (Lunar day with leading zero) | α α¨ |
n |
ααΎα α¬ ααα (Moon status short) | α |
N |
ααΎα α¬ ααα (Moon status full) | ααΎα |
m |
ααα αααααα· (Lunar month) | αα·ααα·α |
M |
αααα»αα·αααα· (Solar month) | ααααΆ |
a |
ααααΆααααα (Animal year) | ααΌα |
e |
ααα (Era year) | ααααΉαααα·ααα |
b |
ααααΆααα»αααααααΆα (Buddhist Era year) | α’α₯α¦α§ |
c |
ααααΆααααα·αααααααΆα (Gregorian year) | α’α α’α€ |
j |
ααααΆαα α»αααααααΆα (Jolak Sakaraj year) | α‘α£α¨α₯ |
$date = new KhmerDate('2024-01-15');
// Basic formats
echo $date->toLunarDate('W'); // α’αΆαα·ααα
echo $date->toLunarDate('dN'); // α¨ααΎα
echo $date->toLunarDate('dN ααm'); // α¨ααΎα αααα·ααα·α
// Complete formats
echo $date->toLunarDate('ααααW dN ααm ααααΆαa e'); // ααααα’αΆαα·ααα α¨ααΎα αααα·ααα·α ααααΆαααΌα ααααΉαααα·ααα
echo $date->toLunarDate('dN ααααW ααm α.α. b'); // α¨ααΎα ααααα’αΆαα·ααα αααα·ααα·α α.α. α’α₯α¦α§
// Mixed calendar formats
echo $date->toLunarDate('ααααW ααΈ d ααM ααααΆα c'); // Mixed lunar/solar format
echo $date->toLunarDate('α.α. b (α.α. c)'); // α.α. α’α₯α¦α§ (α.α. α’α α’α€)
For advanced formatting needs, use the KhmerFormatter
class directly:
use PPhatDev\LunarDate\KhmerFormatter;
$formatter = new KhmerFormatter();
// Number formatting
echo $formatter->toKhmerNumber('2024'); // α’α α’α€
echo $formatter->fromKhmerNumber('α’α α’α€'); // 2024
echo $formatter->formatNumber(1234.56, 2); // α‘,α’α£α€.α₯α¦
// Date formatting
$date = new DateTime('2024-01-15');
echo $formatter->formatDate($date, 'full'); // Full Khmer Gregorian date
echo $formatter->getDayName($date); // α’αΆαα·ααα
echo $formatter->getMonthName($date); // ααααΆ
echo $formatter->getLunarMonthName(5); // αα·ααΆα
// Currency and time formatting
echo $formatter->formatCurrency(1500.50); // α‘,α₯α α .α₯α ααα
echo $formatter->formatTime($date, true); // 24-hour format
echo $formatter->formatTime($date, false); // 12-hour format
// Text validation and utilities
$isKhmer = $formatter->isKhmerText('ααΆααΆααααα'); // true
$isKhmer = $formatter->isKhmerText('English'); // false
echo $formatter->formatOrdinal(21); // α’α‘
// Format lunar date data
$lunarData = KhmerDate::findLunarDate(new DateTime('2024-01-15'));
echo $formatter->formatLunarDate($lunarData, 'full');
This library is a faithful port of the JavaScript momentkh library. Here's how the APIs compare:
const moment = require('moment');
require('@thyrith/momentkh')(moment);
let today = moment();
console.log(today.toLunarDate());
let birthday = moment('1996-09-24');
console.log(birthday.toLunarDate('dN ααααW ααm α.α. b'));
console.log(moment.getKhNewYearMoment(2024));
use PPhatDev\LunarDate\KhmerDate;
$today = new KhmerDate();
echo $today->toLunarDate();
$birthday = new KhmerDate('1996-09-24');
echo $birthday->toLunarDate('dN ααααW ααm α.α. b');
echo KhmerDate::getKhNewYearMoment(2024)->format('Y-m-d H:i');
The Khmer calendar is a sophisticated lunisolar system that combines lunar phases with solar year calculations:
The Khmer calendar has 14 possible months per year:
Regular Months (12 months):
- αα·ααα·α, αα»ααα, ααΆα, ααααα»α, α αααα, αα·ααΆα, ααααα, α’αΆααΆα, ααααΆααα, αααααα, α’αααα»α, ααααα·α
Leap Months (occur in leap years):
- ααααΆααΆα (first α’αΆααΆα)
- αα»αα·ααΆααΆα (second α’αΆααΆα)
Each lunar month follows the moon phases:
- Waxing Moon (ααΎα): α‘ααΎα, α’ααΎα, ... α‘α₯ααΎα (15 days)
- Waning Moon (ααα ): α‘ααα , α’ααα , ... α‘α€ααα or α‘α₯ααα (14-15 days)
Animal Years (12-year cycle): ααΌα, ααααΌα, ααΆα, ααα, ααα, ααααΆαα, αααΈ, ααα, αα, αααΆ, α , αα»α
Era Years (10-year cycle): ααααΉαααα·ααα, α―αααα, ααααα, ααααΈααα, α ααααΆααα, αααα ααα, αααα, ααααααα, α’αααααα, ααααααα
Buddhist Era (BE): Gregorian year + 543/544 (depending on the time of year)
The Khmer calendar has three types of years:
- Regular Year (354 days): 12 months, normal lunar cycle
- Leap Month Year (α’αα·αααΆα): 13 months (384 days) - adds ααααΆααΆα/αα»αα·ααΆααΆα
- Leap Day Year (α ααααααΆαα·ααΆα): Extra day added to ααααα month (355 days)
The library implements complex calculations including:
- Bodithey (ααΌαα·ααΈ): Lunar calendar adjustments
- Avoman (α’αααΆα): Leap month calculations
- Ahargun (α’α ααα»α): Leap day determinations
- Soriyatra Lerng Sak: New Year timing calculations
// Find all full moon days (15ααΎα) in 2024
$fullMoons = Utils::findLunarDayOccurrences(15, 0, 2024);
foreach ($fullMoons as $fullMoon) {
echo "Full Moon: {$fullMoon['date']} - {$fullMoon['khmer']}\n";
}
// Find all new moon days (15ααα
) in 2024
$newMoons = Utils::findLunarDayOccurrences(15, 1, 2024);
// Get all Buddhist holidays for 2024
$holidays = Utils::getBuddhistHolidays(2024);
foreach ($holidays as $holiday) {
echo "{$holiday['name']}: {$holiday['date']} ({$holiday['type']})\n";
}
$date = new KhmerDate('2024-07-15');
$season = Utils::getSeason($date);
echo "Season: {$season['name']} ({$season['name_en']})\n";
echo "Description: {$season['description']}\n";
// Convert between different calendar systems
$currentYear = 2024;
$beYear = Utils::convertEra($currentYear, 'AD', 'BE'); // 2567
$jsYear = Utils::convertEra($currentYear, 'AD', 'JS'); // 1385
// Convert back
$adFromBE = Utils::convertEra($beYear, 'BE', 'AD'); // 2024
echo "Gregorian: $currentYear\n";
echo "Buddhist Era: $beYear\n";
echo "Jolak Sakaraj: $jsYear\n";
// Get all days in a specific Khmer month
$monthData = Utils::getKhmerMonthRange(5, 2567); // αα·ααΆα month, BE 2567
foreach ($monthData as $day) {
echo "Day {$day['day']}: {$day['lunar']} - {$day['gregorian']}\n";
}
// Validate Khmer dates
$isValid = Utils::isValidKhmerDate(15, 5, 2567); // true - valid full moon
$isValid = Utils::isValidKhmerDate(31, 5, 2567); // false - invalid lunar day
# Clone the repository
git clone https://github.com/pphatdev/lunar-date.git
cd lunar-date
# Install dependencies
composer install
# Run examples to test functionality
php examples/demo.php
php examples/momentkh_examples.php
php examples/new_year.php
php examples/current_be_year.php
This library maintains high code quality standards:
# Run complete test suite
composer test
# or
make test
# Generate code coverage report
composer test:coverage
# or
make coverage
# Run all quality checks
make quality
The library includes comprehensive test coverage:
- Unit Tests: All classes and methods tested with PHPUnit
- Integration Tests: End-to-end calendar conversion testing
- Edge Case Testing: Boundary conditions and error scenarios
- Simple Test: Basic functionality verification with
tests/simple_test.php
- β CI/CD Pipeline: GitHub Actions with multi-version PHP testing (7.4, 8.0, 8.1, 8.2, 8.3)
- β PSR-12 Compliance: Modern PHP coding standards
- β Type Safety: Comprehensive type hints and strict types
- β Error Handling: Proper exception handling and validation
- β Documentation: Complete PHPDoc annotations
src/
βββ KhmerDate.php # Main date class with conversion methods
βββ KhmerFormatter.php # Formatting and localization utilities
βββ KhmerCalculator.php # Core calendar calculations and algorithms
βββ SoriyatraLerngSak.php # Khmer New Year calculations
βββ Utils.php # Utility functions and helper methods
βββ Constants.php # Calendar constants and definitions
examples/
βββ demo.php # Basic usage examples and demonstrations
βββ momentkh_examples.php # MomentKH compatibility demos
βββ new_year.php # Khmer New Year calculation examples
βββ current_be_year.php # Buddhist Era year examples
tests/
βββ KhmerDateTest.php # Main class tests
βββ KhmerCalculatorTest.php # Calculation tests
βββ ConstantsTest.php # Constants validation
βββ simple_test.php # Basic functionality test
.github/workflows/
βββ ci.yml # GitHub Actions CI configuration
We welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository and create a feature branch
- Follow PSR-12 coding standards
- Add tests for new functionality
- Update documentation as needed
- Run quality checks before submitting
# Before submitting PR
make quality # Run all quality checks
composer test # Ensure all tests pass
See CONTRIBUTING.md for detailed contribution guidelines.
- Performance Optimization: Improve calculation efficiency
- Extended Formatting: More formatting options and tokens
- Historical Accuracy: Enhance historical date accuracy
- Documentation: Expand examples and use cases
- PHP 7.4+: Modern PHP with type hints support
- No External Dependencies: Uses only PHP standard library
- Composer: For autoloading and dependency management
This project is licensed under the MIT License - see the LICENSE file for details.
- ThyrithSor - Creator of the original momentkh JavaScript library
- Moment.js Team - Inspiration for the API design philosophy
- "Pratitin Soryakkatik-Chankatik 1900-1999" by Mr. Roath Kim Soeun
- cam-cc.org - Cambodian calendar calculations
- dahlina.com - Historical calendar references
Special appreciation to ThyrithSor for creating the foundational momentkh library and making Khmer calendar calculations accessible to developers worldwide. This PHP port aims to bring the same functionality to the PHP ecosystem while maintaining compatibility with the original JavaScript API.
- Documentation: This README and inline PHPDoc comments
- Examples: Check the
examples/
directory for usage patterns - Issues: Report bugs and request features via GitHub Issues
- Discussions: Use GitHub Discussions for questions and community support
Note: This library focuses on historical accuracy and cultural authenticity for Khmer calendar calculations. While extensively tested, please verify critical date calculations for important applications.