Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dev/design/issue-queue.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ resolved before implementation. No new pass or failure is asserted here.

| Code/test boundary | Issue | Priority / state | Evidence, next action and blocker |
|---|---|---|---|
| File metadata | [#1912](https://github.com/fglock/PerlOnJava/issues/1912) `Time::HiRes::stat` | Normal / Active | Daiku 1.004 passes on system Perl and exposed the missing runtime function; the implementation and both-backend regression are in [PR #1913](https://github.com/fglock/PerlOnJava/pull/1913). |
| XML provider APIs | [#1892](https://github.com/fglock/PerlOnJava/issues/1892) XSLT output | High / Active | [PR #1894](https://github.com/fglock/PerlOnJava/pull/1894) is open; verify its serialization fix against HTML::ToDocBook. Coordinate through that work rather than start a duplicate. |
| XML provider APIs | [#1323](https://github.com/fglock/PerlOnJava/issues/1323) `indexElements` | Normal / Ready | Missing Document method is identified. Add standard-Perl-validated focused coverage and Cindy acceptance; check XML provider overlap with #1894. No established ownership prerequisite. |
| Frontend constants/quoting | [#1481](https://github.com/fglock/PerlOnJava/issues/1481) Literal control characters | Normal / Ready | One upstream failure on both backends, Perl passes. Reduce literal `qw` operands before choosing frontend versus regex code; require focused and upstream acceptance. |
Expand Down
2 changes: 2 additions & 0 deletions docs/about/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ priorities and future plans.

## Work in progress

- Implement `Time::HiRes::stat` with high-resolution file timestamps (#1912).

- Export `fieldhash` from `Hash::Util` for compatibility with core Perl.
- Normalize tied-hash `exists` results to Perl's boolean values.

Expand Down
47 changes: 47 additions & 0 deletions src/main/java/org/perlonjava/runtime/perlmodule/TimeHiRes.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
package org.perlonjava.runtime.perlmodule;

import org.perlonjava.runtime.operators.MathOperators;
import org.perlonjava.runtime.operators.Stat;
import org.perlonjava.runtime.operators.Time;
import org.perlonjava.runtime.runtimetypes.GlobalVariable;
import org.perlonjava.runtime.runtimetypes.RuntimeArray;
import org.perlonjava.runtime.runtimetypes.RuntimeContextType;
import org.perlonjava.runtime.runtimetypes.RuntimeIO;
import org.perlonjava.runtime.runtimetypes.RuntimeList;
import org.perlonjava.runtime.runtimetypes.RuntimeScalar;
import org.perlonjava.runtime.runtimetypes.RuntimeScalarType;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.time.Instant;

public class TimeHiRes extends PerlModuleBase {
Expand All @@ -27,6 +36,7 @@ public static void initialize() {
module.registerMethod("usleep", null);
module.registerMethod("nanosleep", null);
module.registerMethod("gettimeofday", null);
module.registerMethod("stat", null);
// getLong() can fetch tied scalars or run numeric overload code.
// Keep call-depth tracking so those callbacks remain visible.
module.registerMethodWithReadOnlyArgsWithoutJvmClosureFrame("clock_gettime", null);
Expand Down Expand Up @@ -68,6 +78,43 @@ public static RuntimeList gettimeofday(RuntimeArray args, int ctx) {
return result;
}

/** Return Perl stat fields with sub-second timestamps where the filesystem provides them. */
public static RuntimeList stat(RuntimeArray args, int ctx) {
RuntimeScalar target = args.isEmpty()
? GlobalVariable.getGlobalVariable("main::_")
: args.get(0);
RuntimeList result = Stat.stat(target, ctx).getList();
if (ctx != RuntimeContextType.LIST || result.elements.size() < 11
|| target.type == RuntimeScalarType.GLOB
|| target.type == RuntimeScalarType.GLOBREFERENCE) {
return result;
}

try {
Path path = RuntimeIO.resolvePath(target.toString());
if (path == null) return result;
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
result.elements.set(8, new RuntimeScalar(epochSeconds(attrs.lastAccessTime())));
result.elements.set(9, new RuntimeScalar(epochSeconds(attrs.lastModifiedTime())));
try {
Object ctime = Files.getAttribute(path, "unix:ctime");
if (ctime instanceof FileTime fileTime) {
result.elements.set(10, new RuntimeScalar(epochSeconds(fileTime)));
}
} catch (UnsupportedOperationException | IllegalArgumentException ignored) {
// Non-Unix filesystems do not expose a distinct status-change time.
}
} catch (IOException | RuntimeException ignored) {
// Keep the ordinary stat result if high-resolution attributes are unavailable.
}
return result;
}

private static double epochSeconds(FileTime fileTime) {
Instant instant = fileTime.toInstant();
return instant.getEpochSecond() + instant.getNano() / 1_000_000_000.0;
}

/** Return a high-resolution clock reading in seconds. */
public static RuntimeList clock_gettime(RuntimeArray args, int ctx) {
long clockId = args.get(0).getLong();
Expand Down
2 changes: 1 addition & 1 deletion src/main/perl/lib/Time/HiRes.pm
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use Exporter 'import';

our $VERSION = '1.9764';
our @EXPORT_OK = qw(usleep nanosleep ualarm gettimeofday tv_interval time sleep alarm
clock_gettime CLOCK_REALTIME CLOCK_MONOTONIC);
stat clock_gettime CLOCK_REALTIME CLOCK_MONOTONIC);

require XSLoader;
XSLoader::load('Time::HiRes');
Expand Down
24 changes: 24 additions & 0 deletions src/test/resources/unit/time_hires_stat.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use strict;
use warnings;
use Test::More;
use File::Temp qw(tempfile);
use Time::HiRes qw(stat);

my ($fh, $path) = tempfile();
print {$fh} "stat test\n";
close $fh or die "close $path: $!";

my @core_stat = CORE::stat($path);
my @qualified_stat = Time::HiRes::stat($path);
is(scalar @qualified_stat, 13, 'qualified stat returns the stat fields');
is($qualified_stat[7], $core_stat[7], 'qualified stat reports the file size');
cmp_ok($qualified_stat[9], '>=', $core_stat[9], 'high-resolution mtime is not before core stat');
cmp_ok($qualified_stat[9], '<', $core_stat[9] + 1, 'high-resolution mtime is within the same second');

my @imported_stat = stat($path);
is($imported_stat[7], $core_stat[7], 'imported stat returns the file size');
ok(Time::HiRes::stat($path), 'qualified stat returns true in scalar context');
is(scalar(Time::HiRes::stat("$path.missing")), '', 'stat on a missing file returns false');

unlink $path or die "unlink $path: $!";
done_testing();
Loading