diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index dc32fa5..ae03e5c 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -11,34 +11,17 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10"]
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.15", "3.16", "3.17", "3.18", "3.19", "3.20"]
steps:
- - uses: actions/checkout@v3
+ - uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Run tests
run: |
python -m pip install --upgrade pip
sh test.sh
- - name: SonarCloud Scan
- if: ${{ matrix.python-version == '3.10'}}
- uses: sonarsource/sonarcloud-github-action@master
- env:
- GITHUB_TOKEN: ${{ secrets.SONAR_GITHUB_TOKEN }}
- SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
- with:
- args: >
- -Dsonar.organization=rogervila-github
- -Dsonar.projectKey=rogervila_python_carbon
- -Dsonar.python.coverage.reportPaths=coverage.xml
- -Dsonar.python.version=3.10
- -Dsonar.sources=.
- -Dsonar.exclusions=tests/**
- -Dsonar.test.exclusions=tests/**
- -Dsonar.tests=tests/
- -Dsonar.verbose=true
diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index a4092e3..9411df9 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -8,38 +8,18 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ["3.5", "3.6", "3.7", "3.8", "3.9", "3.10"]
+ python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v4
+ uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Run tests
run: |
python -m pip install --upgrade pip
sh test.sh
- - name: Get PR author
- id: get_author
- run: echo ::set-output name=AUTHOR::${{ github.event.pull_request.user.login }}
- - name: SonarCloud Scan
- if: ${{ matrix.python-version == '3.10' && steps.get_author.outputs.AUTHOR == 'rogervila' }}
- uses: sonarsource/sonarcloud-github-action@master
- env:
- GITHUB_TOKEN: ${{ secrets.SONAR_GITHUB_TOKEN }}
- SONAR_TOKEN: ${{ secrets.SONARCLOUD_TOKEN }}
- with:
- args: >
- -Dsonar.organization=rogervila-github
- -Dsonar.projectKey=rogervila_python_carbon
- -Dsonar.python.version=3.10
- -Dsonar.python.coverage.reportPaths=coverage.xml
- -Dsonar.sources=.
- -Dsonar.exclusions=tests/**
- -Dsonar.test.exclusions=tests/**
- -Dsonar.tests=tests/
- -Dsonar.verbose=true
diff --git a/.gitignore b/.gitignore
index c5b8004..aeba46a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ build/
htmlcov/
*.dat
MANIFEST
+.vscode/
diff --git a/.pylintrc b/.pylintrc
new file mode 100644
index 0000000..ef15790
--- /dev/null
+++ b/.pylintrc
@@ -0,0 +1,5 @@
+[MASTER]
+ignore=.github
+
+[MESSAGES CONTROL]
+disable = dangerous-default-value,unnecessary-lambda,broad-except,line-too-long,invalid-name,no-name-in-module,missing-module-docstring,missing-class-docstring,missing-function-docstring,eval-used,bare-except,broad-except,too-few-public-methods,import-outside-toplevel,wildcard-import,unused-wildcard-import,pointless-string-statement
diff --git a/LICENSE.txt b/LICENSE.txt
index 2266208..ce8e6b8 100644
--- a/LICENSE.txt
+++ b/LICENSE.txt
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021 Roger Vilà
+Copyright (c) 2026 Roger Vilà
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 1b5888e..0c2b781 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,8 @@

-[](https://sonarcloud.io/dashboard?id=rogervila_python_carbon)
-[](https://sonarcloud.io/dashboard?id=rogervila_python_carbon)
-[](https://sonarcloud.io/dashboard?id=rogervila_python_carbon)
-
+[](https://badge.fury.io/py/python-carbon)
+
PHP Carbon library adapted for python.
diff --git a/setup.py b/setup.py
index ee73f20..6f6dbbd 100644
--- a/setup.py
+++ b/setup.py
@@ -25,5 +25,10 @@
'Topic :: Software Development :: Build Tools',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
+ 'Programming Language :: Python :: 3.11',
+ 'Programming Language :: Python :: 3.12',
+ 'Programming Language :: Python :: 3.13',
+ 'Programming Language :: Python :: 3.14',
],
)
diff --git a/test.sh b/test.sh
index 2542634..a10e09b 100644
--- a/test.sh
+++ b/test.sh
@@ -1,9 +1,9 @@
#!/usr/bin/env sh
pip install --user coverage -U
-pip install --user python-dateutil>=2 -U
+pip install --user "python-dateutil>=2" -U
-coverage run -m unittest discover || exit 1
-coverage xml -i || exit 1
+python -m coverage run -m unittest discover || exit 1
+python -m coverage xml -i || exit 1
[ -f coverage.xml ] || exit 1
diff --git a/tests/test_python_carbon.py b/tests/test_python_carbon.py
index 693763c..f818bff 100644
--- a/tests/test_python_carbon.py
+++ b/tests/test_python_carbon.py
@@ -1,225 +1,137 @@
import unittest
+from datetime import timedelta
from python_carbon import Carbon
class test_python_carbon(unittest.TestCase):
- def test_todo(self):
- self.assertTrue(True)
-
- print('\n----\nTEST\n----\n')
-
+ def test_carbon_methods(self) -> None:
dt = Carbon.parse('2021-08-16 01:02:03')
dt2 = Carbon.parse('2021-08-16 01:02:03')
- print(
- dt.equalTo(dt2)
- )
+ self.assertTrue(dt.equalTo(dt2))
dt = Carbon.now()
+ self.assertIsInstance(dt, Carbon)
+
dt = Carbon() # equivalent to Carbon.now()
+ self.assertIsInstance(dt, Carbon)
dt = Carbon.createFromFormat('%Y-%m-%d', '2021-08-18')
-
- print(
- dt.getTimestamp()
- )
-
- print(
- dt.weekday()
- )
-
- print(
- dt.format('%Y-%m-%d')
- )
-
- print(
- dt.strftime('%Y-%m')
- )
-
- print(
- dt.toDateTimeString()
- )
-
- print(
- dt.addDays(1).setYear(2001).toDateTimeString()
- )
-
- print(
- dt.year
- )
-
- print(
- dt.getYear()
- )
-
- print(
- dt.micro
- )
-
- print(
- dt.timestamp
- )
+ self.assertEqual(dt.format('%Y-%m-%d'), '2021-08-18')
+
+ self.assertEqual(dt.weekday(), Carbon.WEDNESDAY)
+ self.assertEqual(dt.format('%Y-%m-%d'), '2021-08-18')
+ self.assertEqual(dt.strftime('%Y-%m'), '2021-08')
+ self.assertEqual(dt.toDateTimeString(), '2021-08-18 00:00:00')
+ self.assertEqual(dt.addDays(1).setYear(2001).toDateTimeString(), '2001-08-19 00:00:00')
+ self.assertEqual(dt.year, 2021)
+ self.assertEqual(dt.getYear(), 2021)
+ self.assertEqual(dt.micro, 0)
+ self.assertIsInstance(dt.timestamp, float)
dt = Carbon.now()
- print(
- dt.startOfWeek().format('%Y-%m-%d %H:%M:%S.%f')
- )
-
- print(
- dt.endOfWeek().format('%Y-%m-%d %H:%M:%S.%f')
- )
-
- print(
- dt.startOfDay().format('%Y-%m-%d %H:%M:%S.%f')
- )
-
- print(
- dt.endOfDay().format('%Y-%m-%d %H:%M:%S.%f')
- )
-
- print(
- dt.startOf('minute').format('%Y-%m-%d %H:%M:%S.%f')
- )
-
- print(
- dt.endOf('minute').format('%Y-%m-%d %H:%M:%S.%f')
- )
-
- print(
- dt.startOfHour().format('%Y-%m-%d %H:%M:%S.%f')
- )
-
- print(
- dt.endOfHour().format('%Y-%m-%d %H:%M:%S.%f')
- )
-
- print(
- Carbon.parse('2021-08-31').isLastDayOfMonth()
- )
-
- print(
- Carbon.now().addYears(10).toDateTimeString()
- )
-
- print(
- Carbon.now().add(10, 'years').toDateTimeString()
- )
-
- print(
- Carbon.now().subYears(10).toDateTimeString()
- )
-
- print(
- Carbon.now().sub(10, 'years').toDateTimeString()
- )
-
- print(type(Carbon.timedelta(days=1)))
-
- # print(dt.microseconds)
- # print(dt.micro)
-
- print(
- Carbon.now().difference(Carbon.parse('1992-04-17 17:00:00'))
- )
-
- print(
- Carbon.now().diffIn('years', Carbon.parse('1992-04-17 17:00:00'))
- )
-
- print(
- Carbon.now().diffIn('months', Carbon.parse('1992-04-17 17:00:00'))
- )
-
- print(
- Carbon.now().diffIn('weeks', Carbon.parse('1992-04-17 17:00:00'))
- )
-
- print(
- Carbon.now().diffIn('days', Carbon.parse('1992-04-17 17:00:00'))
- )
-
- print(
- Carbon.now().diffIn('hours', Carbon.parse('1992-04-17 17:00:00'))
- )
-
- print(
- Carbon.now().diffIn('minutes', Carbon.parse('1992-04-17 17:00:00'))
- )
-
- print(
- Carbon.now().diffIn('seconds', Carbon.parse('1992-04-17 17:00:00'))
- )
-
- print(
- Carbon.now().diffIn('microseconds', Carbon.parse('1992-04-17 17:00:00'))
- )
-
- print(
- Carbon.utcnow().toCookieString()
- )
-
- print(
- Carbon.now().toISOString()
- )
-
- print(
- Carbon.now().getDayOfYear()
- )
-
- print(
- Carbon.parse('2022-01-02').getDayOfYear()
- )
-
- print(
- Carbon.parse('2022-01-01').getWeekOfYear()
- )
-
- print(
- Carbon.now().getWeekOfMonth()
- )
-
- print(
- Carbon.now().startOfMonth().toDateTimeString(with_milliseconds=True)
- )
-
- print(
- Carbon.now().endOfMonth().toDateTimeString(with_milliseconds=True)
- )
-
- print(
- Carbon.now().startOfYear().toDateTimeString(with_milliseconds=True)
- )
-
- print(
- Carbon.now().endOfYear().toDateTimeString(with_milliseconds=True)
- )
-
- print(
- Carbon.now().getQuarters()
- )
-
- print(
- Carbon.now().getQuarter()
- )
-
- print(
- Carbon.now().betweenIncluded(Carbon.yesterday(), Carbon.tomorrow())
- )
-
- print(
- Carbon.now().betweenExcluded(Carbon.yesterday(), Carbon.tomorrow())
- )
-
- print(
- Carbon.yesterday().betweenIncluded(Carbon.yesterday(), Carbon.tomorrow())
- )
-
- print(
- Carbon.yesterday().betweenExcluded(Carbon.yesterday(), Carbon.tomorrow())
- )
-
- print('\n---\nEND\n---\n')
+ start_of_week = dt.startOfWeek()
+ end_of_week = dt.endOfWeek()
+ start_of_day = dt.startOfDay()
+ end_of_day = dt.endOfDay()
+ start_of_minute = dt.startOf('minute')
+ end_of_minute = dt.endOf('minute')
+ start_of_hour = dt.startOfHour()
+ end_of_hour = dt.endOfHour()
+
+ self.assertEqual(start_of_week.weekday(), Carbon.MONDAY)
+ self.assertTrue(start_of_week.isStartOfDay())
+ self.assertEqual(end_of_week.weekday(), Carbon.SUNDAY)
+ self.assertTrue(end_of_week.isEndOfDay())
+
+ self.assertTrue(start_of_day.isStartOfDay())
+ self.assertTrue(end_of_day.isEndOfDay())
+
+ self.assertEqual(start_of_minute.second, 0)
+ self.assertEqual(start_of_minute.microsecond, 0)
+ self.assertEqual(end_of_minute.second, 59)
+ self.assertEqual(end_of_minute.microsecond, 999999)
+
+ self.assertEqual(start_of_hour.minute, 0)
+ self.assertEqual(start_of_hour.second, 0)
+ self.assertEqual(start_of_hour.microsecond, 0)
+ self.assertEqual(end_of_hour.minute, 59)
+ self.assertEqual(end_of_hour.second, 59)
+ self.assertEqual(end_of_hour.microsecond, 999999)
+
+ self.assertTrue(Carbon.parse('2021-08-31').isLastDayOfMonth())
+
+ anchor = Carbon.now()
+ self.assertTrue(anchor.addYears(10).equalTo(anchor.add(10, 'years')))
+ self.assertTrue(anchor.subYears(10).equalTo(anchor.sub(10, 'years')))
+
+ self.assertIsInstance(Carbon.timedelta(days=1), timedelta)
+
+ reference = Carbon.parse('1992-04-17 17:00:00')
+ now = Carbon.now()
+
+ diff = now.difference(reference)
+ self.assertIsInstance(diff, dict)
+ self.assertEqual(
+ set(diff.keys()),
+ {'years', 'months', 'days', 'leapdays', 'hours', 'minutes', 'seconds', 'microseconds'}
+ )
+
+ diff_years = now.diffIn('years', reference)
+ diff_months = now.diffIn('months', reference)
+ diff_weeks = now.diffIn('weeks', reference)
+ diff_days = now.diffIn('days', reference)
+ diff_hours = now.diffIn('hours', reference)
+ diff_minutes = now.diffIn('minutes', reference)
+ diff_seconds = now.diffIn('seconds', reference)
+ diff_microseconds = now.diffIn('microseconds', reference)
+
+ self.assertGreater(diff_years, 0)
+ self.assertEqual(diff_months, now.difference(reference)['months'] + diff_years * 12)
+ self.assertEqual(diff_weeks, diff_days / 7)
+ self.assertEqual(diff_hours, diff_days * 24)
+ self.assertEqual(diff_minutes, diff_hours * 60)
+ self.assertEqual(diff_seconds, diff_minutes * 60)
+ self.assertEqual(diff_microseconds, diff_seconds * 1000)
+
+ cookie_string = Carbon.utcnow().toCookieString()
+ self.assertIsInstance(cookie_string, str)
+ self.assertIn('UTC', cookie_string)
+
+ iso_string = Carbon.now().toISOString()
+ self.assertRegex(iso_string, r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{6}Z$')
+
+ self.assertIn(Carbon.now().getDayOfYear(), range(1, 367))
+ self.assertEqual(Carbon.parse('2022-01-02').getDayOfYear(), 2)
+ self.assertEqual(Carbon.parse('2022-01-01').getWeekOfYear(), 0)
+
+ week_of_month = Carbon.now().getWeekOfMonth()
+ self.assertIn(week_of_month, range(0, 7))
+
+ start_of_month = Carbon.now().startOfMonth()
+ end_of_month = Carbon.now().endOfMonth()
+ start_of_year = Carbon.now().startOfYear()
+ end_of_year = Carbon.now().endOfYear()
+
+ self.assertRegex(start_of_month.toDateTimeString(with_milliseconds=True), r'^\d{4}-\d{2}-01 00:00:00\.\d{6}$')
+ self.assertRegex(end_of_month.toDateTimeString(with_milliseconds=True), r'^\d{4}-\d{2}-\d{2} 23:59:59\.\d{6}$')
+ self.assertRegex(start_of_year.toDateTimeString(with_milliseconds=True), r'^\d{4}-01-01 00:00:00\.\d{6}$')
+ self.assertRegex(end_of_year.toDateTimeString(with_milliseconds=True), r'^\d{4}-12-31 23:59:59\.\d{6}$')
+
+ quarters = Carbon.now().getQuarters()
+ quarter = Carbon.now().getQuarter()
+ self.assertEqual(len(quarters), 4)
+ self.assertTrue(all(len(q) == 3 for q in quarters))
+ self.assertIn(quarter, range(0, 4))
+
+ current = Carbon.now()
+ yesterday = Carbon.yesterday()
+ tomorrow = Carbon.tomorrow()
+ self.assertTrue(current.betweenIncluded(yesterday, tomorrow))
+ self.assertTrue(current.betweenExcluded(yesterday, tomorrow))
+ self.assertTrue(yesterday.betweenIncluded(yesterday, tomorrow))
+ self.assertFalse(yesterday.betweenExcluded(yesterday, tomorrow))
if __name__ == '__main__':