Date 在RDF4J中操纵日期/时间以进行调试

Date 在RDF4J中操纵日期/时间以进行调试,date,sparql,rdf4j,Date,Sparql,Rdf4j,我在Windows 10 Professional 64位上使用RDF4J 2.2.1。我将有一些对日期/时间敏感的自旋构造函数规则。例如,我可能希望将包含xsd:dateTimedatatype属性的三元组与SPARQL内置的now()函数的输出进行比较。要调试此功能,可以方便地以某种方式操纵RDF4J对日期/时间的感知,而不是操纵系统时钟。我知道有一种通用的商业软件(例如Solution Soft的“时间机器”),通常可以操纵任何Windows进程的时间感知。然而,对于我们这个小小的概念验证

我在Windows 10 Professional 64位上使用RDF4J 2.2.1。我将有一些对日期/时间敏感的自旋构造函数规则。例如,我可能希望将包含
xsd:dateTime
datatype属性的三元组与SPARQL内置的
now()
函数的输出进行比较。要调试此功能,可以方便地以某种方式操纵RDF4J对日期/时间的感知,而不是操纵系统时钟。我知道有一种通用的商业软件(例如Solution Soft的“时间机器”),通常可以操纵任何Windows进程的时间感知。然而,对于我们这个小小的概念验证项目来说,这个软件似乎太贵了

我希望能够做到:

  • 将RDF4J的日期/时间设置为任意的日期/时间值
  • 在调试期间,让RDF4J的日期/时间以实时速度或可编程的更快速度进行
对于如何以这种方式操纵RDF4J的日期/时间,有人有什么建议吗?这将使我对时间敏感的自旋规则的调试更加高效。我宁愿不与电脑的系统时钟抗争,因为许多其他事情都依赖于它。我认为运行整个虚拟PC并在虚拟PC上调试是另一种选择,但似乎应该有一种更简单的方法


谢谢。

您可以通过实现一个并使用它来代替实际的
now()
函数来实现这一点。例如,将其称为
mock_now()
。因为您实现了它,所以您可以完全控制它的行为。

我将我的解决方案发布到我的问题中,希望它可以帮助其他人,作为RDF4J下自定义SPARQL函数的进一步示例。我不认为这是一个优雅的解决方案(因为我是如何设置测试条件的),但它确实可以工作并满足我的要求。此解决方案基于以下内容扩展了@jeen_broekstra的答案:

现在,我在由
前缀soo:
定义的命名空间中实现了一个自定义函数,该函数名为
soo:spectrumOpsDateTime()
,可以接受三个参数,也可以不接受任何参数。三参数情况允许按如下方式设置缩放日期时间

  • 第一个参数:
    xsd:boolean
    。。。如果
    为真,则使用系统时钟;如果
    为假,则使用缩放时钟
  • 第二个参数:
    xsd:dateTime
    (如果第一个参数为true,则忽略)。。。缩放时钟操作的开始日期/时间
  • 第三个参数:
    xsd:double
    (如果第一个参数为true,则忽略)。。。按比例调整的时钟速率(例如,2.0表示按比例调整的时钟以两倍的实时速度运行得更快)
如果没有参数,
soo:spectrumOpsDateTime()
返回缩放的日期/时间或系统日期/时间,具体取决于Java代码中指定的初始值或最后三个参数调用指定的值。测试中的SPARQL和SPIN代码将只使用无参数版本。测试设置查询将设置特定测试的时间条件

下面是一个示例SPARQL设置查询,用于从今天早上开始设置2倍的速度:

PREFIX soo: <http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#>

SELECT DISTINCT *
WHERE {
  BIND(soo:spectrumOpsDateTime("false"^^xsd:boolean, "2017-08-22T10:49:21.019-05:00"^^xsd:dateTime, "2.0"^^xsd:double) AS ?testDateTime) .
}
前缀soo:
选择不同的*
在哪里{
绑定(soo:spectrumOpsDateTime(“false”^^xsd:boolean,“2017-08-22T10:49:21.019-05:00”^^xsd:dateTime,“2.0”^^xsd:double)为testDateTime)。
}
下面是一个SPARQL查询示例,用于获取缩放的日期/时间:

PREFIX soo: <http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#>

SELECT DISTINCT *
WHERE {
  BIND(soo:spectrumOpsDateTime() AS ?testDateTime) .
}
前缀soo:
选择不同的*
在哪里{
绑定(soo:spectrumOpsDateTime()为?testDateTime)。
}
用于实现此自定义函数的单个类是:

/**
 * 
 */

package mil.disa.dso.spo.a2i.nsc.sharing2025.scaledDateTime;

import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

import org.eclipse.rdf4j.model.IRI;
import org.eclipse.rdf4j.model.Literal;
import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
import org.eclipse.rdf4j.query.algebra.evaluation.ValueExprEvaluationException;
import org.eclipse.rdf4j.query.algebra.evaluation.function.Function;

/**
 * Class for generating a configurable date/time clock that can either be a pass-through of the
 * system clock or a scaled clock starting at a specified date/time running at a specified
 * rate from that specified time (first call). 
 * @author Greg Cox of Roberson and Associates &copy Copyright 2017 Roberson and Associates, All Right Reserved
 *
 */
public class DateTimeGenerator implements Function {
    private static final String thisClassName = "RDF4JCustomSPARQLFunction." + DateTimeGenerator.class.getSimpleName();
    private static final String thisClassFullName = DateTimeGenerator.class.getName();
    private static final boolean errorMessages = true;
    private static final boolean verboseMessages = true;

    private double clockPace = 2.0;                     // the speed of the clock, 1.0 is real time, 2.0 is 2x real time (double speed)
    private boolean useSystemClock = false;             // flag to indicate whether to use scaled clock or pass through the system clock

    private ZonedDateTime startingRealDateTime = null;  // the real time stamp at the first call to the evaluate function
    private ZonedDateTime startingScaledDateTime =      // the scaled time stamp (starting scaled time) at the first call to the evaluate function
            ZonedDateTime.parse("2016-08-21T17:29:37.568-05:00");

    // define a constant for the namespace of custom function
    private static String NAMESPACE = "http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#";    // defined as soo: elsewhere





    // this is the evaluate function needed to implement the RDF4J Function interface
    //  it can take 0 or 3 arguments
    //  0 - get the current scaled time (starting by first call)
    //  3 - useSystemClock flag (true/false), starting date/time (xsd:dateTime), clock pace (non-negative real w/ 1.0 meaning 1sec = 1sec)
    @SuppressWarnings("unused")
    @Override
    public Value evaluate(ValueFactory valueFactory, Value... args) throws ValueExprEvaluationException {
        String thisMethodMessagePrefix = "";

        if (errorMessages || verboseMessages ) {
            String thisMethodName = ".evaluate: ";
            thisMethodMessagePrefix = thisClassName + thisMethodName;
        }


        if (args.length == 3) {
            // Three arguments --> attempting to set mode/parameters, so attempt to parse/check them
            if (verboseMessages) System.out.println(thisMethodMessagePrefix + "attempting to set scaled clock mode/parameters");

            boolean argErrFlag = false;
            boolean newUseSystemClock = false;
            String argErrMessage = "";

            // first argument should be true/false on whether to use system clock (true) or scaled clock (false)
            if (!(args[0] instanceof Literal)) {
                argErrFlag = true;
                argErrMessage += "first argument must be a literal true/false value... ";
            } else {
                String useSystemClockString = args[0].stringValue();
                if (useSystemClockString.equalsIgnoreCase("true")) {
                    if (verboseMessages) System.out.println(thisMethodMessagePrefix + "use system clock specified");
                    newUseSystemClock = true;
                } else if (useSystemClockString.equalsIgnoreCase("false")) {
                    if (verboseMessages) System.out.println(thisMethodMessagePrefix + "use scaled clock specified");
                    newUseSystemClock = false;
                }
                else {
                    argErrFlag = true;
                    argErrMessage += "first argument must be a literal true/false value... ";
                }
            }

            // second argument should be starting date/time for scaled clock (ignore if using system clock)
            ZonedDateTime startTime = null;
            if (!newUseSystemClock) { 
                if (!(args[1] instanceof Literal)) {
                    argErrFlag = true;
                    argErrMessage += "second argument must be literal xsd:dateTime value for start of scaled date/time... ";
                } else {
                    String startDateTimeString = args[1].stringValue();
                    try {
                        startTime = ZonedDateTime.parse(startDateTimeString);
                    } catch (Exception e) {
                        argErrFlag = true;
                        argErrMessage += "could not parse starting date/time... " + e.getMessage() + "... ";
                    }
                }
            }

            // third argument should be clock pace for scaled clock (ignore if using system clock)
            Double newClockPace = null;
            if (!newUseSystemClock) {
                if (!(args[2] instanceof Literal)) {
                    argErrFlag = true;
                    argErrMessage += "third argument must be literal xsd:double value for clock pace... ";
                } else {
                    String clockPaceString = args[2].stringValue();
                    try {
                        newClockPace = Double.parseDouble(clockPaceString);
                    } catch (Exception e) {
                        argErrFlag = true;
                        argErrMessage += "could not parse clock pace which should be a positive xsd:double... ";
                    }
                    if ((newClockPace != null) && (newClockPace <= 0.0)) {
                        argErrFlag = true;
                        argErrMessage += "clock pace must be positive, got " + newClockPace + "... ";
                    }
                }
            }

            // check for errors and set up the generator if no errors...
            if (argErrFlag) {
                if (errorMessages) System.err.println(thisMethodMessagePrefix + "ERROR - " + argErrMessage);
                if (errorMessages) System.err.println(thisMethodMessagePrefix + "throwing exception...");
                throw new ValueExprEvaluationException(
                        "spectrum operations time function soo:spectrumOpsDateTime() encountered errors in function arguments... " +
                                argErrMessage);
            } else if (newUseSystemClock) {
                if (verboseMessages) System.out.println(thisMethodMessagePrefix + "using unscaled system clock");
                useSystemClock = newUseSystemClock;
            } else if (!newUseSystemClock) {
                if (verboseMessages) System.out.println(thisMethodMessagePrefix + "using scaled time");
                useSystemClock = newUseSystemClock;
                startingRealDateTime = ZonedDateTime.now();
                if (verboseMessages) System.out.println(thisMethodMessagePrefix + "setting starting real time to " + startingRealDateTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
                if (verboseMessages) System.out.println(thisMethodMessagePrefix + "setting start time to " + startTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
                startingScaledDateTime = startTime;
                if (verboseMessages) System.out.println(thisMethodMessagePrefix + "setting clock pace to " + String.format("%5.2f", newClockPace * 100.0) + "%");
                clockPace = newClockPace;
            }

        } else if (args.length != 0) {  // can only have no arguments or three arguments...
            throw new ValueExprEvaluationException(
                    "spectrum operations time function soo:spectrumOpsDateTime() requires "
                            + "zero arguments or three arguments, got "
                            + args.length + " arguments");
        }

        // now run the generator and return the result...

        IRI xsdDateTimeIRI = valueFactory.createIRI("http://www.w3.org/2001/XMLSchema#dateTime");  // long-form equivalent to xsd:dateTime

        if (useSystemClock) {
            String unscaledTimeString = millisTrailingZeroes(ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
            return valueFactory.createLiteral(unscaledTimeString, xsdDateTimeIRI);
        } else {
            errString = null;
            String scaledTimeString = millisTrailingZeroes(getScaledDateTime().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
            if (scaledTimeString == null) {
                if (errorMessages) System.err.println(thisMethodMessagePrefix + "ERROR - scaled time returned null");
                if (errorMessages) System.err.println(thisMethodMessagePrefix + "thowing exception...");
                throw new ValueExprEvaluationException("could not generate valid scaled time string" + ((errString == null) ? "" : "... " + errString));
            }
            return valueFactory.createLiteral(scaledTimeString, xsdDateTimeIRI);
        }
    }

    private static String errString = null;

    /**
     * Utility method to make all the millisecond fields of an <tt>ISO_OFFSET_DATE_TIME</tt> three digits by
     * adding trailing zeroes as needed.  Why? Because of trouble with various implementations interpreting
     * 1 and 2 digit milliseconds differently.  Should be standard decimal, but sometimes interpreted 
     * as number of milliseconds (e.g. .39T interpreted as 39 millieconds inststead of 390 milliseconds)
     * @param <tt>ISO_OFFSET_DATE_TIME</tt> string to check for millisecond field length
     * @return <tt>ISO_OFFSET_DATE_TIME</tt> strnig with trailing zeroes in milliseconds field
     * as require to make the field three digits or <tt>null</tt> on error
     */
    private static String millisTrailingZeroes(String isoDateTimeString) {
        if (isoDateTimeString == null) {
            errString = "DateTimeGenerator.millisTrailingZeroes: got null isoDateTimeString argument, returning null...";
            return null;
        }

        String[] ss_l1 = isoDateTimeString.split("\\.");    // Example: 2017-08-18T13:01:05.39-05:00 --> 2017-08-18T13:01:05 AND 39-05:00
        if (ss_l1.length != 2) {
            errString = "DateTImeGenerator.millisTrailingZeros: first parsing split of isoDateTimeString=" + isoDateTimeString + " by '.' got unexpected number of parts=" + ss_l1.length;
            return null;
        }

        String[] ss_l2 = ss_l1[1].split("-");               // 39-05:00 --> 39 AND 05:00
        if (ss_l2.length != 2) {
            errString = "DateTImeGenerator.millisTrailingZeros: second parsing split of " + ss_l1[1] + " by '-' got unexpected number of parts=" + ss_l2.length;
            return null;
        }

        if (ss_l2[0].length() == 1) {
            ss_l2[0] = ss_l2[0] + "00";
        } else if (ss_l2[0].length() == 2)
            ss_l2[0] = ss_l2[0] + "0";                      // 39 --> 390

        return ss_l1[0] + "." + ss_l2[0] + "-" + ss_l2[1];  // 2017-08-18T13:01:05.390-05:00
    }

    /**
     * Method to get the current scaled date time according to the state of this DateTimeGenerator.
     * If <tt>useSystemClock</tt> is <tt>true</tt>, then time is not 
     * scaled and system time is returned instead of scaled time.
     * @return scaled date time if <tt>useSystemClock</tt> is <tt>true</tt> or
     * system date time if <tt>useSystemClock</tt> is <tt>false</tt>
     */
    private ZonedDateTime getScaledDateTime() {
        ZonedDateTime scaledDateTime = null;

        if (useSystemClock) {
            scaledDateTime = ZonedDateTime.now();
        } else {
            if (startingRealDateTime == null) 
                startingRealDateTime = ZonedDateTime.now();
            long realMillisFromFirstCall = ChronoUnit.MILLIS.between(startingRealDateTime, ZonedDateTime.now());
            long scaledMillisFromFirstCall = (long) ((double) realMillisFromFirstCall * clockPace);

            scaledDateTime = ChronoUnit.MILLIS.addTo(startingScaledDateTime, scaledMillisFromFirstCall);
        }

        return scaledDateTime;
    }


    @Override
    public String getURI() {
        return NAMESPACE + "spectrumOpsDateTime";
    }

    /**
     * Test main method
     * @param args command line arguments (ignored)
     */
    @SuppressWarnings("unused")
    public static void main(String[] args) {
        String thisMethodMessagePrefix = "";

        if (errorMessages || verboseMessages ) {
            String thisMethodName = ".main: ";
            thisMethodMessagePrefix = thisClassName + thisMethodName;
        }

        DateTimeGenerator testGen = new DateTimeGenerator();

        if (verboseMessages) System.out.println(thisMethodMessagePrefix + "custom SPARQL method URI: " + testGen.getURI());
        if (verboseMessages) System.out.println(thisMethodMessagePrefix + "fully-qualified class name: " + thisClassFullName);

        ValueFactory testVF = SimpleValueFactory.getInstance();
        Value testValues[] = new Value[0];

        while (true) {

            if (verboseMessages) System.out.println(thisMethodMessagePrefix + "scaled: " + testGen.evaluate(testVF, testValues).stringValue() +
                    " current real: " + millisTrailingZeroes(ZonedDateTime.now().format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
/**
* 
*/
文件包mil.disa.dso.spo.a2i.nsc.sharing2025.scaledDateTime;
导入java.time.ZonedDateTime;
导入java.time.format.DateTimeFormatter;
导入java.time.temporal.ChronoUnit;
导入org.eclipse.rdf4j.model.IRI;
导入org.eclipse.rdf4j.model.Literal;
导入org.eclipse.rdf4j.model.Value;
导入org.eclipse.rdf4j.model.ValueFactory;
导入org.eclipse.rdf4j.model.impl.SimpleValueFactory;
导入org.eclipse.rdf4j.query.algebra.evaluation.ValueExprEvaluationException;
导入org.eclipse.rdf4j.query.algebra.evaluation.function.function;
/**
*类生成可配置的日期/时间时钟,该时钟可以是
*系统时钟或从指定日期/时间开始、以指定时间运行的按比例缩放的时钟
*指定时间(第一次呼叫)的费率。
*@作者Greg Cox of Roberson and Associates&版权所有2017 Roberson and Associates版权所有
*
*/
公共类DateTimeGenerator实现函数{
私有静态最终字符串thisClassName=“RDF4JCustomSPARQLFunction。”+DateTimeGenerator.class.getSimpleName();
私有静态最终字符串thisClassFullName=DateTimeGenerator.class.getName();
私有静态最终布尔errorMessages=true;
私有静态最终布尔verboseMessages=true;
专用双时钟空间=2.0;//时钟的速度,1.0为实时,2.0为2倍实时(双速)
私有布尔值useSystemClock=false;//指示是使用缩放时钟还是通过系统时钟的标志
private ZoneDateTime startingRealDateTime=null;//第一次调用求值函数时的实时戳
private ZonedDateTime startingScaledDateTime=//第一次调用evaluate函数时的缩放时间戳(开始缩放时间)
ZoneDateTime.parse(“2016-08-21T17:29:37.568-05:00”);
//为自定义函数的命名空间定义一个常量
私有静态字符串命名空间=”http://www.disa.mil/dso/a2i/ontologies/PBSM/Sharing/SpectrumOperationsOntology#“;//定义为soo:别处
//这是实现RDF4J函数接口所需的求值函数
//它可以接受0或3个参数
//0-获取当前缩放时间(从第一次调用开始)
//3-使用系统时钟标志(真/假)、开始日期/时间(xsd:dateTime)、时钟速度(