aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock17
-rw-r--r--Cargo.toml1
-rw-r--r--crates/shirabe-php-shim/Cargo.toml1
-rw-r--r--crates/shirabe-php-shim/src/datetime.rs58
-rw-r--r--crates/shirabe-php-shim/src/runtime.rs2
-rw-r--r--crates/shirabe-symfony-console/src/command/complete_command.rs2
-rw-r--r--crates/shirabe/src/command/show_command.rs6
-rw-r--r--crates/shirabe/src/console/application.rs20
-rw-r--r--crates/shirabe/src/util/github.rs6
-rw-r--r--crates/shirabe/src/util/perforce.rs6
-rw-r--r--crates/shirabe/tests/command/show_command_test.rs4
-rw-r--r--crates/shirabe/tests/common/bootstrap.rs3
-rw-r--r--docs/known-incompatibilities.md18
13 files changed, 83 insertions, 61 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 880eb700..86c1c272 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -179,6 +179,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e"
[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2189,6 +2195,7 @@ dependencies = [
"tar",
"tempfile",
"twox-hash",
+ "tzfile",
"zip",
]
@@ -2683,6 +2690,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de"
[[package]]
+name = "tzfile"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f59c22c42a2537e4c7ad21a4007273bbc5bebed7f36bc93730a5780e22a4592e"
+dependencies = [
+ "byteorder",
+ "chrono",
+]
+
+[[package]]
name = "unicode-general-category"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 6f674171..75a88e65 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -54,6 +54,7 @@ tokio = { version = "1.52.3", features = ["full"] }
tracing = "0.1.44"
tracing-subscriber = { version = "0.3.23", features = ["env-filter", "fmt"] }
twox-hash = "2.1.2"
+tzfile = "0.1.3"
url = "2.5.8"
zip = "8.6.0"
diff --git a/crates/shirabe-php-shim/Cargo.toml b/crates/shirabe-php-shim/Cargo.toml
index 791cb805..cad9ad47 100644
--- a/crates/shirabe-php-shim/Cargo.toml
+++ b/crates/shirabe-php-shim/Cargo.toml
@@ -27,6 +27,7 @@ sha1.workspace = true
sha2.workspace = true
tar.workspace = true
twox-hash.workspace = true
+tzfile.workspace = true
zip.workspace = true
[dev-dependencies]
diff --git a/crates/shirabe-php-shim/src/datetime.rs b/crates/shirabe-php-shim/src/datetime.rs
index fd0348ff..28ec52af 100644
--- a/crates/shirabe-php-shim/src/datetime.rs
+++ b/crates/shirabe-php-shim/src/datetime.rs
@@ -1,11 +1,8 @@
-static DEFAULT_TIMEZONE: std::sync::Mutex<Option<String>> = std::sync::Mutex::new(None);
-
/// Parse the subset of the strtotime()/date_create() grammar that Composer actually emits.
///
/// Supported: ISO8601/RFC3339 (`2023-01-15T12:34:56Z`, `...+00:00`), `Y-m-d H:i:s`, `Y-m-d`,
-/// and `@<unixtime>`. Inputs without an explicit offset are interpreted as UTC, matching the
-/// default timezone this shim assumes elsewhere. Anything else returns `None` rather than
-/// guessing, mirroring PHP returning `false` on unrecognized input.
+/// and `@<unixtime>`. Inputs without an explicit offset are interpreted as UTC. Anything else
+/// returns `None` rather than guessing, mirroring PHP returning `false` on unrecognized input.
fn parse_to_fixed(s: &str) -> Option<chrono::DateTime<chrono::FixedOffset>> {
let s = s.trim();
if s.is_empty() {
@@ -126,34 +123,35 @@ pub fn microtime() -> f64 {
duration.as_secs_f64()
}
-// PHP defaults to "UTC" when no default timezone has been configured.
-pub fn date_default_timezone_get() -> String {
- DEFAULT_TIMEZONE
- .lock()
- .unwrap()
- .clone()
- .unwrap_or_else(|| "UTC".to_string())
-}
-
-pub fn date_default_timezone_set(tz: &str) -> bool {
- *DEFAULT_TIMEZONE.lock().unwrap() = Some(tz.to_string());
- true
+/// PHP: `date()`, rendering in UTC.
+pub fn date_utc(format: &str, timestamp: Option<i64>) -> String {
+ let timestamp = timestamp.unwrap_or_else(time);
+ let dt = chrono::DateTime::<chrono::Utc>::from_timestamp(timestamp, 0)
+ .expect("date() timestamp out of range");
+ dt.format(date_format_to_strftime(format)).to_string()
}
-pub fn date(format: &str, timestamp: Option<i64>) -> String {
+/// PHP: `date()`, rendering in the system's local timezone.
+pub fn date_local(format: &str, timestamp: Option<i64>) -> String {
let timestamp = timestamp.unwrap_or_else(time);
- // TODO(php-semantics): model the system default timezone. PHP `date()` renders in the default
- // timezone (usually the system's local zone); without a timezone database only "UTC" can be
- // resolved here, so on a non-UTC machine this diverges whenever the local date differs from
- // the UTC date (e.g. daily 00:00-09:00 JST). Fixing this needs a timezone database (a new
- // crate). Any named zone is rejected loudly rather than silently rendered in the wrong zone.
- let tz = date_default_timezone_get();
- if tz != "UTC" {
- panic!(
- "date() with non-UTC default timezone {tz:?} is not supported (no timezone database)"
- );
- }
let dt = chrono::DateTime::<chrono::Utc>::from_timestamp(timestamp, 0)
.expect("date() timestamp out of range");
- dt.format(date_format_to_strftime(format)).to_string()
+ let tz = local_timezone();
+ dt.with_timezone(&&tz)
+ .format(date_format_to_strftime(format))
+ .to_string()
+}
+
+/// The zone `$TZ` names, or the one `/etc/localtime` describes. Falls back to UTC when no tz
+/// database is readable, as PHP does when `date.timezone` is unset.
+fn local_timezone() -> tzfile::Tz {
+ #[cfg(unix)]
+ {
+ tzfile::Tz::local().unwrap_or_else(|_| tzfile::Tz::from(chrono::Utc))
+ }
+ #[cfg(not(unix))]
+ {
+ // TODO(windows): `tzfile::Tz::local()` is Unix-only.
+ todo!()
+ }
}
diff --git a/crates/shirabe-php-shim/src/runtime.rs b/crates/shirabe-php-shim/src/runtime.rs
index 6fba5774..fcd6a50f 100644
--- a/crates/shirabe-php-shim/src/runtime.rs
+++ b/crates/shirabe-php-shim/src/runtime.rs
@@ -67,8 +67,6 @@ pub fn function_exists(name: &str) -> bool {
| "curl_multi_setopt"
| "curl_share_init"
| "curl_strerror"
- | "date_default_timezone_get"
- | "date_default_timezone_set"
| "disk_free_space"
| "exec"
| "filter_var"
diff --git a/crates/shirabe-symfony-console/src/command/complete_command.rs b/crates/shirabe-symfony-console/src/command/complete_command.rs
index 8058d698..dc418178 100644
--- a/crates/shirabe-symfony-console/src/command/complete_command.rs
+++ b/crates/shirabe-symfony-console/src/command/complete_command.rs
@@ -276,7 +276,7 @@ impl Command for CompleteCommand {
String::new(),
format!(
"<comment>{}</>",
- shirabe_php_shim::date("Y-m-d H:i:s", None)
+ shirabe_php_shim::date_utc("Y-m-d H:i:s", None)
),
"<info>Input:</> <comment>(\"|\" indicates the cursor position)</>".to_string(),
format!(" {}", completion_input.to_string()),
diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs
index e5d5a1f2..f2246cbe 100644
--- a/crates/shirabe/src/command/show_command.rs
+++ b/crates/shirabe/src/command/show_command.rs
@@ -39,8 +39,8 @@ use indexmap::IndexMap;
use shirabe_pcre::{CaptureKey, Preg};
use shirabe_php_shim::{
CmpOp, DATE_ATOM, InvalidArgumentException, LogicException, PhpMixed, UnexpectedValueException,
- array_search, date, date_format_to_strftime, extension_loaded, impl_php_class, in_array_loose,
- in_array_strict, php_regex, realpath, strtolower, version_compare,
+ array_search, date_format_to_strftime, date_local, extension_loaded, impl_php_class,
+ in_array_loose, in_array_strict, php_regex, realpath, strtolower, version_compare,
};
use shirabe_semver::Semver;
use shirabe_semver::constraint::AnyConstraint;
@@ -1484,7 +1484,7 @@ impl ShowCommand {
if release_date
.format(date_format_to_strftime("Ymd"))
.to_string()
- == date("Ymd", None)
+ == date_local("Ymd", None)
{
return "today".to_string();
}
diff --git a/crates/shirabe/src/console/application.rs b/crates/shirabe/src/console/application.rs
index 3e4a746b..9a17868c 100644
--- a/crates/shirabe/src/console/application.rs
+++ b/crates/shirabe/src/console/application.rs
@@ -57,13 +57,12 @@ use crate::util::Silencer;
use indexmap::IndexMap;
use shirabe_php_shim::Catch as _;
use shirabe_php_shim::{
- LogicException as ShimLogicException, PhpMixed, RuntimeException, bin2hex, chdir,
- date_default_timezone_get, date_default_timezone_set, defined, dirname, disk_free_space,
- extension_loaded, file_exists, file_get_contents, file_put_contents, function_exists, getcwd,
- getmypid, glob, ini_set, is_array, is_dir, is_file, is_string, json_decode,
- memory_get_peak_usage, memory_get_usage, microtime, php_regex, php_uname, posix_getuid,
- random_bytes, realpath, restore_error_handler, round, str_replace, strpos, strtoupper,
- sys_get_temp_dir, time, unlink,
+ LogicException as ShimLogicException, PhpMixed, RuntimeException, bin2hex, chdir, defined,
+ dirname, disk_free_space, extension_loaded, file_exists, file_get_contents, file_put_contents,
+ function_exists, getcwd, getmypid, glob, ini_set, is_array, is_dir, is_file, is_string,
+ json_decode, memory_get_peak_usage, memory_get_usage, microtime, php_regex, php_uname,
+ posix_getuid, random_bytes, realpath, restore_error_handler, round, str_replace, strpos,
+ strtoupper, sys_get_temp_dir, time, unlink,
};
use shirabe_seld_json_lint::ParsingException;
use shirabe_symfony_console::application::Application as BaseApplication;
@@ -157,13 +156,6 @@ impl Application {
ini_set("xdebug.scream", "0");
}
- if function_exists("date_default_timezone_set")
- && function_exists("date_default_timezone_get")
- {
- let tz = Silencer::call(|| Ok(date_default_timezone_get())).unwrap_or_default();
- date_default_timezone_set(&tz);
- }
-
let io: std::rc::Rc<std::cell::RefCell<dyn IOInterface>> =
std::rc::Rc::new(std::cell::RefCell::new(NullIO::new()));
diff --git a/crates/shirabe/src/util/github.rs b/crates/shirabe/src/util/github.rs
index 26756ed0..25a95e57 100644
--- a/crates/shirabe/src/util/github.rs
+++ b/crates/shirabe/src/util/github.rs
@@ -10,7 +10,7 @@ use crate::util::ProcessExecutor;
use indexmap::IndexMap;
use shirabe_pcre::{CaptureKey, Preg};
use shirabe_php_shim::Catch as _;
-use shirabe_php_shim::{PhpMixed, date, in_array_loose, php_regex, stripos, strtolower};
+use shirabe_php_shim::{PhpMixed, date_local, in_array_loose, php_regex, stripos, strtolower};
#[derive(Debug)]
pub struct GitHub {
@@ -106,7 +106,7 @@ impl GitHub {
note += &format!(" on {}", output.trim());
}
}
- note += &format!(" {}", date("Y-m-d Hi", None));
+ note += &format!(" {}", date_local("Y-m-d Hi", None));
let (local_name, auth_name): (Option<String>, String) = {
let cfg = self.config.borrow();
@@ -309,7 +309,7 @@ impl GitHub {
let ts: i64 = value.trim().parse().unwrap_or(0);
rate_limit.insert(
"reset".to_string(),
- PhpMixed::String(date("Y-m-d H:i:s", Some(ts))),
+ PhpMixed::String(date_local("Y-m-d H:i:s", Some(ts))),
);
}
_ => {}
diff --git a/crates/shirabe/src/util/perforce.rs b/crates/shirabe/src/util/perforce.rs
index 1820936c..246e37f5 100644
--- a/crates/shirabe/src/util/perforce.rs
+++ b/crates/shirabe/src/util/perforce.rs
@@ -8,7 +8,7 @@ use crate::util::ProcessExecutor;
use indexmap::IndexMap;
use shirabe_pcre::Preg;
use shirabe_php_shim::{
- Exception, PHP_EOL, PhpMixed, PhpResource, chdir, date, explode, fclose, feof, fgets,
+ Exception, PHP_EOL, PhpMixed, PhpResource, chdir, date_local, explode, fclose, feof, fgets,
file_get_contents, fopen, fwrite, gethostname, json_decode, php_regex, str_replace_array,
strcmp, strlen, strpos, strrpos, substr, time, trim,
};
@@ -414,7 +414,7 @@ impl Perforce {
spec,
format!(
"Update: {}{}{}",
- date("Y/m/d H:i:s", None),
+ date_local("Y/m/d H:i:s", None),
PHP_EOL,
PHP_EOL
),
@@ -422,7 +422,7 @@ impl Perforce {
);
fwrite(
spec,
- format!("Access: {}{}", date("Y/m/d H:i:s", None), PHP_EOL),
+ format!("Access: {}{}", date_local("Y/m/d H:i:s", None), PHP_EOL),
None,
);
fwrite(
diff --git a/crates/shirabe/tests/command/show_command_test.rs b/crates/shirabe/tests/command/show_command_test.rs
index 47eae462..20dbf848 100644
--- a/crates/shirabe/tests/command/show_command_test.rs
+++ b/crates/shirabe/tests/command/show_command_test.rs
@@ -8,7 +8,7 @@ use serial_test::serial;
use shirabe::package::Link;
use shirabe::package::handle::PackageInterfaceHandle;
use shirabe::repository::PlatformRepository;
-use shirabe_php_shim::PhpMixed;
+use shirabe_php_shim::{PhpMixed, date_local};
/// Build a `Vec<(PhpMixed, PhpMixed)>` command input from `(key, value)` pairs.
fn input(pairs: Vec<(&str, PhpMixed)>) -> Vec<(PhpMixed, PhpMixed)> {
@@ -1028,7 +1028,7 @@ fn test_self_and_package_combination() {
match and prints \"this week\" whenever the local date differs from the UTC date \
(e.g. daily 00:00-09:00 JST); see TODO(php-semantics) in shirabe-php-shim datetime.rs"]
fn test_self() {
- let today = chrono::Local::now().format("%Y-%m-%d").to_string();
+ let today = date_local("Y-m-d", None);
let _tear_down = init_temp_composer(
Some(&serde_json::json!({
"name": "vendor/package",
diff --git a/crates/shirabe/tests/common/bootstrap.rs b/crates/shirabe/tests/common/bootstrap.rs
index a908bff2..9f667597 100644
--- a/crates/shirabe/tests/common/bootstrap.rs
+++ b/crates/shirabe/tests/common/bootstrap.rs
@@ -18,9 +18,6 @@ pub fn bootstrap() {
ONCE.call_once(|| {
// PHP: error_reporting(E_ALL) has no counterpart here.
- // PHP: date_default_timezone_set(@date_default_timezone_get());
- shirabe_php_shim::date_default_timezone_set(&shirabe_php_shim::date_default_timezone_get());
-
// PHP: require src/bootstrap.php and refresh vendor/composer/InstalledVersions.php.
// TODO(php-runtime): port remaining bootstrap processes (the src/bootstrap.php include and
// the InstalledVersions refresh are PHP autoload mechanics with no Rust counterpart yet).
diff --git a/docs/known-incompatibilities.md b/docs/known-incompatibilities.md
index 79ead94c..d90b926b 100644
--- a/docs/known-incompatibilities.md
+++ b/docs/known-incompatibilities.md
@@ -75,3 +75,21 @@ Reflection on objects a plugin creates itself works as usual.
`ob_*()` functions work as usual in PHP, but cannot capture any output from
Rust side.
+
+
+## Misc.
+
+### Default Timezone
+
+PHP resolves the default timezone from the `date.timezone` INI setting, and
+falls back to UTC when it is unset. Shirabe does not read php.ini: it uses the
+system's local timezone instead and falls back to UTC when no tz database is
+available.
+
+Only the date time for humans are affected, such as the reset time of the
+GitHub API rate limit. The machine-readable time, e.g., timestamps written to
+`composer.lock` or `vendor/composer/installed.json` are recorded in UTC in both
+Composer and Shirabe.
+
+Plugins and scripts run in the PHP worker, where `date.timezone` is applied as
+usual.