diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 4aeb38e48fb34da640f693a497a0d52be51f7699..0266a1311c82811cffde906e28ea16f9fff0e387 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -49,7 +49,7 @@ Please read their documentations on how to use them the best possible.
 - [DuniterPy](https://clients.pages.duniter.org/python/duniterpy/index.html): Autogenerated documentation.
   - Feel free to contribute upstream to share the code with other Python programs
 - [Click](https://click.palletsprojects.com/#documentation)
-- [Pendulum](https://pendulum.eustace.io/docs/)
+- [Arrow](https://arrow.readthedocs.io/)
 - [texttable](https://github.com/foutaise/texttable/#documentation)
 
 ## Pre-commit hooks
diff --git a/README.md b/README.md
index 35400bc3cfb063d88f7023155cb69f456358f0f3..cc4aa33af4f5ec030ecb5c8ccf9399c1636ae203 100644
--- a/README.md
+++ b/README.md
@@ -119,7 +119,7 @@ Silkaj is based on following Python modules:
 
 - [Click](https://click.palletsprojects.com/): Composable command line interface toolkit
 - [DuniterPy](https://git.duniter.org/clients/python/duniterpy/): Most complete client oriented Python library for Duniter/Äž1 ecosystem
-- [Pendulum](https://pendulum.eustace.io/): Datetimes made easy
+- [Arrow](https://arrow.readthedocs.io/): Better dates & times for Python
 - [texttable](https://github.com/foutaise/texttable/): Creation of simple ASCII tables
 
 ### Names
diff --git a/pyproject.toml b/pyproject.toml
index 34e8734ce1c0abf36fe8c44334217111dddfcd9e..93182c33150470b7d22cee454bfc9639ee4bb971 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -18,6 +18,7 @@ python = "^3.9.0"
 duniterpy = "~1.2.0"
 rich-click = "^1.8.3"
 texttable = "^1.7.0"
+arrow = "^1.3.0"
 pendulum = "^3.0.0"
 pydiscourse = { version = "^1.7.0", optional = true }
 
diff --git a/silkaj/blockchain/blocks.py b/silkaj/blockchain/blocks.py
index 7b7d9d7b9db94721bf261d6866d82575c649866b..99dbcc5ddafb4bac71e2eef01b4f3a385b6a60e5 100644
--- a/silkaj/blockchain/blocks.py
+++ b/silkaj/blockchain/blocks.py
@@ -17,9 +17,9 @@ import time
 from operator import itemgetter
 from urllib.error import HTTPError
 
+import arrow
 import rich_click as click
 from duniterpy.api import bma
-from pendulum import from_timestamp
 
 from silkaj import tui
 from silkaj.blockchain.tools import get_head_block
@@ -49,11 +49,11 @@ def list_blocks(number: int, detailed: bool) -> None:
         issuer = {}
         issuer["pubkey"] = block["issuer"]
         if detailed or number <= 30:
-            gentime = from_timestamp(block["time"], tz="local").format(ALL)
-            mediantime = from_timestamp(block["medianTime"], tz="local").format(ALL)
             issuer["block"] = block["number"]
-            issuer["gentime"] = gentime
-            issuer["mediantime"] = mediantime
+            issuer["gentime"] = arrow.get(block["time"]).to("local").format(ALL)
+            issuer["mediantime"] = (
+                arrow.get(block["medianTime"]).to("local").format(ALL)
+            )
             issuer["hash"] = block["hash"][:10]
             issuer["powMin"] = block["powMin"]
         issuers_dict[issuer["pubkey"]] = issuer
diff --git a/silkaj/blockchain/difficulty.py b/silkaj/blockchain/difficulty.py
index fc65d14bf7d8484b3c47eefe13a90f2ee97cb65b..51fd99a402a35f7b8c073fb3ff5c1a5b5af6c111 100644
--- a/silkaj/blockchain/difficulty.py
+++ b/silkaj/blockchain/difficulty.py
@@ -16,11 +16,11 @@
 from operator import itemgetter
 from os import system
 
+import arrow
 import jsonschema
 import rich_click as click
 from duniterpy.api import bma
 from duniterpy.api.client import WSConnection
-from pendulum import from_timestamp
 from websocket._exceptions import WebSocketConnectionClosedException
 
 from silkaj import network, tui
@@ -54,7 +54,7 @@ def display_diffi(current: WSConnection, diffi: dict) -> None:
         d["Π diffi"] = compute_power(match_pattern(d["level"])[1])
         d["Σ diffi"] = d.pop("level")
     system("cls||clear")
-    block_gen = from_timestamp(current["time"], tz="local").format(ALL)
+    block_gen = arrow.get(current["time"]).to("local").format(ALL)
     match = match_pattern(int(current["powMin"]))[0]
 
     table = tui.Table(style="columns").set_cols_dtype(["t", "t", "t", "i"])
diff --git a/silkaj/blockchain/information.py b/silkaj/blockchain/information.py
index 693f5322779158be38a0391b8287c87ed139ed5b..bfd31467360af9c8ae6d0ca3ce93715f0854c13f 100644
--- a/silkaj/blockchain/information.py
+++ b/silkaj/blockchain/information.py
@@ -13,8 +13,8 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
 
+import arrow
 import rich_click as click
-from pendulum import from_timestamp
 
 from silkaj.blockchain.tools import get_head_block
 from silkaj.constants import ALL
@@ -26,8 +26,8 @@ from silkaj.tools import get_currency_symbol
 def currency_info() -> None:
     head_block = get_head_block()
     ep = determine_endpoint()
-    current_time = from_timestamp(head_block["time"], tz="local")
-    mediantime = from_timestamp(head_block["medianTime"], tz="local")
+    current_time = arrow.get(head_block["time"]).to("local")
+    mediantime = arrow.get(head_block["medianTime"]).to("local")
     print(
         "Connected to node:",
         ep.host,
@@ -45,5 +45,5 @@ def currency_info() -> None:
         "\nMedian time:",
         mediantime.format(ALL),
         "\nDifference time:",
-        current_time.diff_for_humans(mediantime, True),
+        current_time - mediantime,
     )
diff --git a/silkaj/constants.py b/silkaj/constants.py
index 1b6e9cff8100c97e0d3d8721249e79ff2199e87c..6a35aaad6977affae7202c5b2bfa4a0b1e3287e4 100644
--- a/silkaj/constants.py
+++ b/silkaj/constants.py
@@ -39,10 +39,9 @@ MINIMAL_RELATIVE_TX_AMOUNT = 1e-6
 CENT_MULT_TO_UNIT = 100
 SHORT_PUBKEY_SIZE = 8
 
-# pendulum constants
-# see https://pendulum.eustace.io/docs/#localized-formats
-DATE = "LL"
-HOUR = "LTS"
-ALL = "LLL"
+# Arrow date time formats
+# https://arrow.readthedocs.io/en/latest/guide.html#supported-tokens
+DATE = "MMMM D, YYYY"
+ALL = "MMMM D, YYYY hh:mm A ZZZ"
 # Not ISO 8601 compliant but common
 ALL_DIGITAL = "YYYY-MM-DD HH:mm:ss"
diff --git a/silkaj/money/history.py b/silkaj/money/history.py
index e3a523ba10a7eca054525b668e95cfbe78701a4c..cb92dfa9e5be14d1d12a1b6ec1701309b08e281c 100644
--- a/silkaj/money/history.py
+++ b/silkaj/money/history.py
@@ -19,12 +19,12 @@ from pathlib import Path
 from typing import Any, Optional
 from urllib.error import HTTPError
 
+import arrow
 import rich_click as click
 from duniterpy.api.bma.tx import history
 from duniterpy.api.client import Client
 from duniterpy.documents.transaction import OutputSource, Transaction
 from duniterpy.grammars.output import Condition
-from pendulum import from_timestamp, now
 
 from silkaj.constants import ALL, ALL_DIGITAL
 from silkaj.money.tools import (
@@ -109,7 +109,7 @@ def generate_header(pubkey: str, currency_symbol: str, ud_value: int) -> str:
         idty = {"uid": ""}
     balance = get_amount_from_pubkey(pubkey)
     balance_ud = round(balance[1] / ud_value, 2)
-    date = now().format(ALL)
+    date = arrow.now().format(ALL)
     return f'Transactions history from: {idty["uid"]} {gen_pubkey_checksum(pubkey)}\n\
 Current balance: {balance[1] / 100} {currency_symbol}, {balance_ud} UD {currency_symbol} on {date}\n'
 
@@ -202,7 +202,7 @@ def parse_received_tx(
     identities = wt.identities_from_pubkeys(issuers, uids)
     for received_tx in received_txs:
         tx_list = []
-        tx_list.append(from_timestamp(received_tx.time, tz="local").format(ALL_DIGITAL))
+        tx_list.append(arrow.get(received_tx.time).to("local").format(ALL_DIGITAL))
         tx_list.append("")
         for i, issuer in enumerate(received_tx.issuers):
             tx_list[1] += prefix(None, None, i) + assign_idty_from_pubkey(
@@ -243,7 +243,7 @@ def parse_sent_tx(
     identities = wt.identities_from_pubkeys(pubkeys, uids)
     for sent_tx in sent_txs:
         tx_list = []
-        tx_list.append(from_timestamp(sent_tx.time, tz="local").format(ALL_DIGITAL))
+        tx_list.append(arrow.get(sent_tx.time).to("local").format(ALL_DIGITAL))
 
         total_amount, outputs = tx_amount(sent_tx, pubkey, sent_func)
         if len(outputs) > 1:
diff --git a/silkaj/wot/certification.py b/silkaj/wot/certification.py
index 4aab6f82fc6c9729b7b5b75ab7b61bd6fdca7248..a4e5b1fa2e91326be3c7093c874bc210c63b933b 100644
--- a/silkaj/wot/certification.py
+++ b/silkaj/wot/certification.py
@@ -15,12 +15,12 @@
 
 import sys
 
+import arrow
 import rich_click as click
 from duniterpy.api import bma
 from duniterpy.api.client import Client
 from duniterpy.documents import Block, BlockID, Certification, Identity, get_block_id
 from duniterpy.key import SigningKey
-from pendulum import from_timestamp, now
 
 from silkaj import tui
 from silkaj.auth import auth_method
@@ -103,7 +103,7 @@ def pre_checks(client: Client, issuer_pubkey: str, pubkey_to_certify: str) -> di
             # ÄžT: 0<->4.8m - 4.8m + 12.5d
             renewable = cert["expiresIn"] - params["sigValidity"] + params["sigReplay"]
             if renewable > 0:
-                renewable_date = now().add(seconds=renewable).format(DATE)
+                renewable_date = arrow.now().shift(seconds=renewable).format(DATE)
                 sys.exit(f"Certification renewable from {renewable_date}")
 
     # Check if the certification is already in the pending certifications
@@ -125,7 +125,7 @@ def certification_confirmation(
     idty_timestamp = idty_to_certify["meta"]["timestamp"]
     block_id_idty = get_block_id(idty_timestamp)
     block = client(bma.blockchain.block, block_id_idty.number)
-    timestamp_date = from_timestamp(block["time"], tz="local").format(ALL)
+    timestamp_date = arrow.get(block["time"]).to("local").format(ALL)
     block_id_date = f": #{idty_timestamp[:15]}… {timestamp_date}"
     cert.append(["ID", issuer["uid"], "->", idty_to_certify["uid"] + block_id_date])
     cert.append(
@@ -137,8 +137,8 @@ def certification_confirmation(
         ],
     )
     params = bc_tools.get_blockchain_parameters()
-    cert_ends = now().add(seconds=params["sigValidity"]).format(DATE)
-    cert.append(["Valid", now().format(DATE), "—>", cert_ends])
+    cert_ends = arrow.now().shift(seconds=params["sigValidity"]).format(DATE)
+    cert.append(["Valid", arrow.now().format(DATE), "—>", cert_ends])
 
     table = tui.Table()
     table.fill_rows(
diff --git a/silkaj/wot/exclusions.py b/silkaj/wot/exclusions.py
index ccc1fcf06714b4bf57e56468c1d221bd84b56a67..586792d6a489416fe3edf911391b47403efec541 100644
--- a/silkaj/wot/exclusions.py
+++ b/silkaj/wot/exclusions.py
@@ -19,7 +19,7 @@ import sys
 import time
 import urllib
 
-import pendulum
+import arrow
 import rich_click as click
 from duniterpy import constants as dp_const
 from duniterpy.api.bma import blockchain
@@ -222,9 +222,10 @@ def generate_identity_info(lookup, block, params):
     for i, certified in enumerate(lookup["signed"]):
         info += elements_inbetween_list(i, lookup["signed"])
         info += "@" + certified["uid"]
-    dt = pendulum.from_timestamp(block.mediantime + constants.ONE_HOUR, tz="local")
-    info += ".\n- **Exclu·e le** " + dt.format("LLLL", locale="fr")
-    info += " CET\n- **Raison de l'exclusion** : "
+    dt = arrow.get(block.mediantime).shift(hours=1).to(tz="local")
+    arrow_format = "dddd D MMMM YYYY HH:mm ZZZ"
+    info += ".\n- **Exclu·e le** " + dt.format(arrow_format, locale="fr")
+    info += "\n- **Raison de l'exclusion** : "
     if nbr_different_certifiers < params["sigQty"]:
         info += "manque de certifications"
     else:
diff --git a/silkaj/wot/idty_tools.py b/silkaj/wot/idty_tools.py
index 6ba3bbae94eeb8cadfb1c6b80e56d82a987c3994..8722044eb4e226d1ec706a2c8e9157ed745b8e30 100644
--- a/silkaj/wot/idty_tools.py
+++ b/silkaj/wot/idty_tools.py
@@ -18,7 +18,7 @@ import sys
 import urllib
 from typing import Union
 
-import pendulum
+import arrow
 import rich_click as click
 from duniterpy.api import bma
 from duniterpy.documents import BlockID, Identity, Revocation
@@ -40,9 +40,7 @@ def display_identity(idty: Identity) -> Texttable:
     id_table.append(["User ID", idty.uid])
     id_table.append(["Blockstamp", str(idty.block_id)])
     creation_block = client(bma.blockchain.block, idty.block_id.number)
-    creation_date = pendulum.from_timestamp(creation_block["time"], tz="local").format(
-        ALL,
-    )
+    creation_date = arrow.get(creation_block["time"]).to("local").format(ALL)
     id_table.append(["Created on", creation_date])
     # display infos
     table = Texttable(max_width=shutil.get_terminal_size().columns)
diff --git a/silkaj/wot/membership.py b/silkaj/wot/membership.py
index c6edd96a421e59071cd9baa329bd676ed4e29b37..b820017dc52868f4fe694fa8b5483e53851a23fa 100644
--- a/silkaj/wot/membership.py
+++ b/silkaj/wot/membership.py
@@ -16,7 +16,7 @@
 import logging
 import sys
 
-import pendulum
+import arrow
 import rich_click as click
 from duniterpy.api import bma
 from duniterpy.documents import BlockID, Membership, get_block_id
@@ -109,7 +109,7 @@ def display_confirmation_table(
 
     table = []
     if membership_expires:
-        expires = pendulum.now().add(seconds=membership_expires).diff_for_humans()
+        expires = arrow.now().shift(seconds=membership_expires).humanize()
         table.append(["Expiration date of current membership", expires])
 
     if pending_memberships:
@@ -123,7 +123,7 @@ def display_confirmation_table(
         table.append(
             [
                 "Pending membership documents will expire",
-                pendulum.now().add(seconds=pending_expires).diff_for_humans(),
+                arrow.now().shift(seconds=pending_expires).humanize(),
             ],
         )
 
@@ -136,7 +136,7 @@ def display_confirmation_table(
     table.append(
         [
             "Identity published",
-            pendulum.from_timestamp(block["time"], tz="local").format(DATE),
+            arrow.get(block["time"]).to("local").format(DATE),
         ],
     )
 
@@ -144,14 +144,14 @@ def display_confirmation_table(
     table.append(
         [
             "Expiration date of new membership",
-            pendulum.now().add(seconds=params["msValidity"]).diff_for_humans(),
+            arrow.now().shift(seconds=params["msValidity"]).humanize(),
         ],
     )
 
     table.append(
         [
             "Expiration date of new membership from the mempool",
-            pendulum.now().add(seconds=params["msPeriod"]).diff_for_humans(),
+            arrow.now().shift(seconds=params["msPeriod"]).humanize(),
         ],
     )
 
diff --git a/silkaj/wot/status.py b/silkaj/wot/status.py
index 6fdfb75ee4fa7a8f65ca676434299f466e202c2f..2fdd87500c927fad2362da196c36afff5f6c447b 100644
--- a/silkaj/wot/status.py
+++ b/silkaj/wot/status.py
@@ -13,9 +13,9 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
 
+import arrow
 import rich_click as click
 from duniterpy.api.bma import blockchain, wot
-from pendulum import from_timestamp, now
 
 from silkaj.blockchain.tools import get_blockchain_parameters
 from silkaj.constants import DATE
@@ -58,7 +58,7 @@ def status(uid_pubkey: str) -> None:
         for req_cert in req["certifications"]:
             if req_cert["from"] == lookup_cert["pubkey"]:
                 certifications["received_expire"].append(
-                    now().add(seconds=req_cert["expiresIn"]).format(DATE),
+                    arrow.now().shift(seconds=req_cert["expiresIn"]).format(DATE),
                 )
                 certifications["received"].append(f'{lookup_cert["uids"][0]} ✔')
                 break
@@ -67,7 +67,7 @@ def status(uid_pubkey: str) -> None:
             f'{(wt.identity_of(pending_cert["from"]))["uid"]} ✘',
         )
         certifications["received_expire"].append(
-            from_timestamp(pending_cert["expires_on"], tz="local").format(DATE),
+            arrow.get(pending_cert["expires_on"]).to("local").format(DATE),
         )
     certifications["sent"], certifications["sent_expire"] = get_sent_certifications(
         signed,
@@ -103,12 +103,14 @@ def membership_status(certifications: dict, pubkey: str, req: dict) -> None:
     is_member = bool(member_lookup)
     print("member:", is_member)
     if req["revoked"]:
-        revoke_date = from_timestamp(req["revoked_on"], tz="local").format(DATE)
+        revoke_date = arrow.get(req["revoked_on"]).to("local").format(DATE)
         print(f"revoked: {req['revoked']}\nrevoked on: {revoke_date}")
     if not is_member and req["wasMember"]:
         print("expired:", req["expired"], "\nwasMember:", req["wasMember"])
     elif is_member:
-        expiration_date = now().add(seconds=req["membershipExpiresIn"]).format(DATE)
+        expiration_date = (
+            arrow.now().shift(seconds=req["membershipExpiresIn"]).format(DATE)
+        )
         print(f"Membership document expiration: {expiration_date}")
         print("Sentry:", req["isSentry"])
     print("outdistanced:", req["outdistanced"])
@@ -143,7 +145,7 @@ def expiration_date_from_block_id(
         date_approximation(block_id, time_first_block, params["avgGenTime"])
         + params["sigValidity"]
     )
-    return from_timestamp(expir_timestamp, tz="local").format(DATE)
+    return arrow.get(expir_timestamp).to("local").format(DATE)
 
 
 def date_approximation(block_id, time_first_block, avgentime):
diff --git a/tests/unit/wot/test_idty_tools.py b/tests/unit/wot/test_idty_tools.py
index d4cb31d553d2e71a15abac42d213091c390cf596..edc157c820f05e8225d78b36ed9b289b2ec42fd8 100644
--- a/tests/unit/wot/test_idty_tools.py
+++ b/tests/unit/wot/test_idty_tools.py
@@ -16,7 +16,7 @@
 import re
 import urllib
 
-import pendulum
+import arrow
 import pytest
 from duniterpy.api import bma
 from duniterpy.documents.block_id import BlockID
@@ -292,7 +292,7 @@ def test_display_identity(idty, monkeypatch, capsys):
     assert "| User ID    | Claude" in result
     assert "| Blockstamp | 597334" in result
     # idty_block returns a block at timestamp 1594980185,.
-    creation_time = pendulum.from_timestamp(idty_block["time"], tz="local").format(ALL)
+    creation_time = arrow.get(idty_block["time"]).to("local").format(ALL)
     assert creation_time in result
 
 
diff --git a/tests/unit/wot/test_membership.py b/tests/unit/wot/test_membership.py
index 67defd9271d80882511396bf4f79b04e079bcd8d..91c225fb795539c2f3348cd891f14adb9ec36c0f 100644
--- a/tests/unit/wot/test_membership.py
+++ b/tests/unit/wot/test_membership.py
@@ -13,7 +13,7 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with Silkaj. If not, see <https://www.gnu.org/licenses/>.
 
-import pendulum
+import arrow
 import pytest
 from duniterpy.api import bma
 from duniterpy.documents import Membership, get_block_id
@@ -73,7 +73,7 @@ def test_display_confirmation_table(patched_wot_requirements, monkeypatch, capsy
         table.append(
             [
                 "Expiration date of current membership",
-                pendulum.now().add(seconds=membership_expires).diff_for_humans(),
+                arrow.now().shift(seconds=membership_expires).humanize(),
             ],
         )
 
@@ -87,7 +87,7 @@ def test_display_confirmation_table(patched_wot_requirements, monkeypatch, capsy
         table.append(
             [
                 "Pending membership documents will expire",
-                pendulum.now().add(seconds=pending_expires).diff_for_humans(),
+                arrow.now().shift(seconds=pending_expires).humanize(),
             ],
         )
 
@@ -100,7 +100,7 @@ def test_display_confirmation_table(patched_wot_requirements, monkeypatch, capsy
     table.append(
         [
             "Identity published",
-            pendulum.from_timestamp(block["time"], tz="local").format(DATE),
+            arrow.get(block["time"]).to("local").format(DATE),
         ],
     )
 
@@ -108,14 +108,14 @@ def test_display_confirmation_table(patched_wot_requirements, monkeypatch, capsy
     table.append(
         [
             "Expiration date of new membership",
-            pendulum.now().add(seconds=params["msValidity"]).diff_for_humans(),
+            arrow.now().shift(seconds=params["msValidity"]).humanize(),
         ],
     )
 
     table.append(
         [
             "Expiration date of new membership from the mempool",
-            pendulum.now().add(seconds=params["msPeriod"]).diff_for_humans(),
+            arrow.now().shift(seconds=params["msPeriod"]).humanize(),
         ],
     )