def list_course(graph, student): q = prepareQuery('''PREFIX ex: <http://example.org/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT DISTINCT ?course ?g WHERE { ?student foaf:name ?studentName . ?student ?course ?g . }''') qres = graph.query(q, initBindings={'studentName': student}) for row in qres: print(row)
def course_topics(course, graph): q = prepareQuery('''PREFIX ex: <http://example.org/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT DISTINCT ?t ?topics WHERE { ?course ex:covers ?topics. ?topics foaf:name ?t. }''') qres = graph.query(q, initBindings={'course': course}) for row in qres: print(row)
def get_variables_SP(SP): #Get CVs vars_Set = set() vars_Set.update(re.findall("\$\w+", SP)) #Get RVs result_Set = set() query_object = processor.prepareQuery(SP) for var in query_object.algebra['PV']: result_Set.add(var.n3()) return vars_Set, result_Set
def test_15_prepared_qyery(self): from rdflib.plugins.sparql.processor import prepareQuery pquery = prepareQuery("SELECT * { ?s <b> tst:c }", { "tst": "http://example.com/ns/" }, "http://example.com/ns/") TST=Namespace('http://example.com/ns/') self.graph.add((TST.a, TST.b, TST.c)) self.graph.add((TST.d, TST.e, TST.f)) result = self.graph.query(pquery) assert result.type == "SELECT", result.type assert len(result) == 1
def test_15_prepared_qyery(self): from rdflib.plugins.sparql.processor import prepareQuery pquery = prepareQuery("SELECT * { ?s <b> tst:c }", {"tst": "http://example.com/ns/"}, "http://example.com/ns/") TST = Namespace('http://example.com/ns/') self.graph.add((TST.a, TST.b, TST.c)) self.graph.add((TST.d, TST.e, TST.f)) result = self.graph.query(pquery) assert result.type == "SELECT", result.type assert len(result) == 1
def list_student(graph, topic): q = prepareQuery('''PREFIX ex: <http://example.org/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT DISTINCT ?student WHERE { ?course ex:Covers ?topic. ?s ?course ?grade. ?s foaf:name ?student FILTER(?grade != 'F') }''') qres = graph.query(q, initBindings={'topic': topic}) for row in qres: print(row)
def list_topics(graph, student): q = prepareQuery('''PREFIX ex: <http://example.org/> PREFIX foaf: <http://xmlns.com/foaf/0.1/> SELECT DISTINCT ?t WHERE { ?student foaf:name ?studentName ?student ex:COMPLETED ?course . ?student ?course ?grade . ?course ex:Covers ?t . FILTER(?grade != 'F') }''') qres = graph.query(q, initBindings={'studentName': student}) for row in qres: print(row)
def generate_annotation_id(self, collection_uri): collection_id = self._get_id(collection_uri) prefix = collection_uri + "/annotation/" if API.last_generated_ann_id is None: query = prepareQuery('select ?annotation where {?annotation a ?type}') annotations = [] g = Store() g.attach_directory(os.path.join(self.basedir, collection_id)) results = g.graph.query(query, initBindings={'type': DADA.Annotation}) for result in results.bindings: annotations.append(int(result["annotation"].toPython().replace(prefix, ""))) API.last_generated_ann_id = max(annotations) API.last_generated_ann_id += 1 return prefix + str(API.last_generated_ann_id)
def isaBaseQuery(self, queryString, queryObj=None): """ If the given SPARQL query involves purely base predicates it returns it (as a parsed string), otherwise it returns a SPARQL algebra instance for top-down evaluation using this store >>> import rdflib >>> graph = rdflib.Graph() >>> topDownStore = TopDownSPARQLEntailingStore(graph.store, graph, derivedPredicates=[RDFS.seeAlso], nsBindings={u"rdfs": str(RDFS)}) >>> rt=topDownStore.isaBaseQuery("SELECT * { [] rdfs:seeAlso [] }") >>> isinstance(rt,(BasicGraphPattern, AlgebraExpression)) True >>> rt=topDownStore.isaBaseQuery("SELECT * { [] a [] }") >>> isinstance(rt,(Query, basestring)) #doctest: +SKIP True >>> rt=topDownStore.isaBaseQuery("SELECT * { [] a [] OPTIONAL { [] rdfs:seeAlso [] } }") >>> isinstance(rt,(BasicGraphPattern, AlgebraExpression)) True """ # from rdflib.plugins.sparql.sparql import Prologue # from rdflib.plugins.sparql.parser import parseQuery from rdflib.plugins.sparql.processor import prepareQuery from rdflib.plugins.sparql import sparql as sparqlModule if queryObj: query = queryObj else: query = prepareQuery(queryString, initNs=self.nsBindings) # if not query.prologue: # query.prologue = Prologue() # query.prologue.prefixBindings.update(self.nsBindings) # else: # for prefix, nsInst in list(self.nsBindings.items()): # if prefix not in query.prologue.prefixBindings: # query.prologue.prefixBindings[prefix] = nsInst sparqlModule.prologue = query.prologue algebra = RenderSPARQLAlgebra(query, initNs=self.nsBindings) return first(self.getDerivedPredicates( algebra, sparqlModule.prologue)) and algebra or query
def monkeypatch_prepare_query(): """ ensures that rdflib.plugins.sparql.processor is uptodate, else monkeypatch it. """ # pylint: disable=invalid-name import rdflib.plugins.sparql.processor as sparql_processor _TEST_PREPARED_QUERY = sparql_processor.prepareQuery("ASK { ?s ?p ?o }") if not hasattr(_TEST_PREPARED_QUERY, "_original_args"): # monkey-patch 'prepare' original_prepareQuery = sparql_processor.prepareQuery def monkeypatched_prepareQuery(queryString, initNS=None, base=None): """ A monkey-patched version of the original prepareQuery, adding an attribute '_original_args' to the result. """ if initNS is None: initNS = {} ret = original_prepareQuery(queryString, initNS, base) ret._original_args = (queryString, initNS, base) return ret sparql_processor.prepareQuery = monkeypatched_prepareQuery LOG.info("monkey-patched rdflib.plugins.sparql.processor.prepareQuery")
def monkeypatch_prepare_query(): """ ensures that rdflib.plugins.sparql.processor is uptodate, else monkeypatch it. """ # pylint: disable=invalid-name import rdflib.plugins.sparql.processor as sparql_processor _TEST_PREPARED_QUERY = sparql_processor.prepareQuery("ASK { ?s ?p ?o }") if not hasattr(_TEST_PREPARED_QUERY, "_original_args"): # monkey-patch 'prepare' original_prepareQuery = sparql_processor.prepareQuery def monkeypatched_prepareQuery(queryString, initNS=None, base=None): """ A monkey-patched version of the original prepareQuery, adding an attribute '_original_args' to the result. """ if initNS is None: initNS = {} ret = original_prepareQuery(queryString, initNS, base) ret._original_args = (queryString, initNS, base) return ret sparql_processor.prepareQuery = monkeypatched_prepareQuery log.info("monkey-patched rdflib.plugins.sparql.processor.prepareQuery")
def _load_rules(self, rules_file_name): # Rules are stored in hash map rules = {} # Load the rule base into memory logger.debug('Loading {}'.format(rules_file_name)) g = Graph().parse(rules_file_name, format="turtle") # Extract the namespaces from the rule base r_ns = {} for (ns, uri) in g.namespaces(): r_ns[ns] = uri # Compose and prepare the SPARQL queries for s in g.subjects(RDF.type, INDEXER.Rule): # Extract the components of the rule r_if = g.value(s, INDEXER['if']).toPython().replace('\t', '') r_then = g.value(s, INDEXER['then']).toPython().replace('\t', '') rule = 'CONSTRUCT {' + r_then + '} WHERE {' + r_if + '}' # Pre-load the rule rules[s.toPython()] = prepareQuery(rule, initNs=r_ns) return rules
def get_annotations(self, item_uri, filters): """Return the annotations for this item as a dictionary""" result = {'@context': "https://app.alveo.edu.au/schema/json-ld", 'commonProperties': {}, } if filters is None: filters = {} anns = [] initBindings = {"item":URIRef(item_uri)} query = prepareQuery("""select ?annotation where { ?annotation dada:partof ?annCollection. ?annCollection dada:annotates ?item. }""", initNs={"dada":DADA}) if "priorTo" in filters: initBindings = {"item":URIRef(item_uri), "givenTime":Literal(filters["priorTo"].strftime('%Y-%m-%dT%I:%M:%S'), datatype=XSD.dateTime)} query = prepareQuery("""select ?annotation where { ?annotation dada:partof ?annCollection. ?annCollection dada:annotates ?item. ?annCollecction prov:generatedAtTime ?time. FILTER (?time < ?givenTime) }""", initNs={"dada":DADA, "prov":PROV}) elif "user" in filters: initBindings = {"item":URIRef(item_uri), "user":Literal(filters["user"])} query = prepareQuery("""select ?annotation where { ?annotation dada:partof ?annCollection. ?annCollection dada:annotates ?item. ?annCollection prov:generatedBy ?activity. ?activity prov:wasAssociatedWith ?user. }""", initNs={"dada":DADA, "prov":PROV}) elif "type" in filters: initBindings = {"item":URIRef(item_uri), "type":URIRef(self._denamespace(filters["type"]))} query = prepareQuery("""select ?annotation where { ?annotation dada:partof ?annCollection. ?annCollection dada:annotates ?item. ?annotation dada:type ?type }""", initNs={"dada":DADA}) annResults = self.graph.query(query, initBindings=initBindings) annIDs = [annResult["annotation"] for annResult in annResults.bindings] for annid in annIDs: ann = { '@id': str(annid), '@type': '', 'label': str(self.graph.value(subject=annid, predicate=DADA.label)), 'type': str(self.graph.value(subject=annid, predicate=DADA.type)), 'start': '', 'end': '', } region = self.graph.value(subject=annid, predicate=DADA.targets) ann['start'] = self.graph.value(subject=region, predicate=DADA.start).toPython() ann['end'] = self.graph.value(subject=region, predicate=DADA.end).toPython() atype = self.graph.value(subject=region, predicate=RDF.type) if atype == DADA.UTF8Region: ann['@type'] = 'dada:TextAnnotation' elif atype == DADA.SecondRegion: ann['@type'] = 'dada:SecondAnnotation' for _,p,o in self.graph.triples((annid, None, None)): if p not in [DADA.label, DADA.type, DADA.targets, RDF.type, DADA.partof]: ann[p.n3(self.graph.namespace_manager)] = o.toPython() anns.append(ann) result['alveo:annotations'] = anns result['commonProperties']['alveo:annotates'] = self._get_display_document_url(item_uri) return result
def parser_sparql(query_string, schema): query_object = processor.prepareQuery(query_string) # print(query_object.algebra) # print("\n\n") return parser_algebra(query_object.algebra, schema)
def _empty(self): """I remove all obsels from this trace. Compared to ``remove(None, None, None)``, this method leaves the information about the obsel collection itself. """ with self.edit(_trust=True) as editable: trace_uri = editable.value(None, KTBS.hasObselCollection, self.uri) editable.remove((None, None, None)) self.init_graph(editable, self.uri, trace_uri) FIND_LAST_OBSEL = prepareQuery(""" PREFIX : <http://liris.cnrs.fr/silex/2009/ktbs#> SELECT ?o { ?o :hasEnd ?e . FILTER ( !BOUND(?last_end) || (?e >= ?last_end) ) } ORDER BY DESC(?e) LIMIT 1 """) _REFRESH_VALUES = { "no": 0, "default": 1, "yes": 2, "force": 2, "recursive": 3, None: 1, }
"http://ontologies.ti-semantics.com/platform#platform", "http://ontologies.ti-semantics.com/cti#revoked", "http://ontologies.ti-semantics.com/cti#revokedBy", "http://ontologies.ti-semantics.com/cti#tactic", "http://ontologies.ti-semantics.com/cti#alias", # some version that isn't normative but we might include anyway "http://ti-semantics.com/attack#mitreVersion", "http://ontologies.ti-semantics.com/cti#killChainPhase", "http://ontologies.ti-semantics.com/cti#observedData", "http://ontologies.ti-semantics.com/cti#detectionBypassed", "http://ontologies.ti-semantics.com/cti#hasDetection", "http://ti-semantics.com/attack#mitreRemoteSupport" # I don't know what this means ] qprds = prepareQuery("""SELECT DISTINCT ?e WHERE { ?sub ?e ?obj . FILTER(STRSTARTS(STR(?e), ?base)) }""") checked_objects = checked_predicates + [ "http://ontologies.ti-semantics.com/cti#AttackPattern", "http://ontologies.ti-semantics.com/core#Reference", "http://ontologies.ti-semantics.com/cti#IntrusionSet", "http://ontologies.ti-semantics.com/cti#Malware", "http://ontologies.ti-semantics.com/cti#CourseOfAction", "http://ontologies.ti-semantics.com/cti#Tool", "http://ontologies.ti-semantics.com/score#CVSSv3HighPrivilegesRequired", "http://ontologies.ti-semantics.com/score#CVSSv3LowPrivilegesRequired", "http://ontologies.ti-semantics.com/cti#Matrix", "http://ontologies.ti-semantics.com/core#Platform", "http://ontologies.ti-semantics.com/cti#Tactic",
"""I override :func:`rdfrest.util.bounded_description` for obsels. In order to clearly differenciate attributes from relations, related obsels must be linked to the trace by the ktbs:hasTrace. :param node: the node (uri or blank) to return a description of :param graph: the graph from which to retrieve the description :param fill: if provided, fill this graph rather than a fresh one, and return it """ ret = bounded_description(node, graph, fill) trace_uri = ret.value(node, KTBS.hasTrace) add = ret.add for other, in graph.query(_RELATED_OBSELS, initBindings = { "obs": node }): add((other, KTBS.hasTrace, trace_uri)) return ret _RELATED_OBSELS = prepareQuery(""" SELECT DISTINCT ?other { { ?obs ?pred ?other . } UNION { ?other ?pred ?obs . } ?obs <%s> ?trace . ?other <%s> ?trace . } """ % (KTBS.hasTrace, KTBS.hasTrace)) _NON_ALPHA = re.compile(r'[^\w]+') _NOW = datetime.now
parent = self.get_parent() super(BaseMixin, self).remove() parent.force_state_refresh() ######## Private methods ######## def _iter_contained(self): """ Yield the URI and type of every element of this base. """ return iter(self.state.query(_ITER_CONTAINED_QUERY, initBindings={"base": self.uri})) _ITER_CONTAINED_QUERY = prepareQuery(""" PREFIX k: <http://liris.cnrs.fr/silex/2009/ktbs#> SELECT DISTINCT ?s ?t WHERE { $base k:contains ?s . ?s a ?t . } """) # NB: the register_wrapper below does not mean that KTBS.DataGraph is the # *only* type corresponding to InBaseMixin, but this particulat type has no # mixin of its own, so we register it directly here. @register_wrapper(KTBS.DataGraph) @extend_api class InBaseMixin(KtbsResourceMixin): """ Common mixin for all elements of a trace base. """ #pylint: disable-msg=R0903
######## Private methods ######## def _iter_contained(self): """ Yield the URI and type of every element of this base. """ for s, t, _ in iter( self.state.query(_ITER_CONTAINED_QUERY, initBindings={"base": self.uri})): yield s, t _ITER_CONTAINED_QUERY = prepareQuery(""" PREFIX k: <http://liris.cnrs.fr/silex/2009/ktbs#> SELECT DISTINCT ?s ?t $base # selected solely to please Virtuoso WHERE { $base k:contains ?s . ?s a ?t . } """) # NB: the register_wrapper below does not mean that KTBS.DataGraph is the # *only* type corresponding to InBaseMixin, but this particulat type has no # mixin of its own, so we register it directly here. @register_wrapper(KTBS.DataGraph) @extend_api class InBaseMixin(KtbsResourceMixin): """ Common mixin for all elements of a trace base. """ #pylint: disable-msg=R0903
Only obsels can be posted to a trace. """ # unused arguments #pylint: disable=W0613 # self is not used #pylint: disable=R0201 return Obsel # the following query gets all the candidate obsels in a POSTed graph, # and orders them correctly, guessing implicit values _SELECT_CANDIDATE_OBSELS = prepareQuery(""" PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> PREFIX : <%s#> SELECT ?obs (IF(bound(?b), ?b, "INF"^^xsd:float) as ?begin) (IF(bound(?e), ?e, ?begin) as ?end) WHERE { ?obs :hasTrace ?trace OPTIONAL { ?obs :hasBegin ?b } OPTIONAL { ?obs :hasEnd ?e } } ORDER BY ?begin ?end """ % KTBS_NS_URI) class ComputedTrace(ComputedTraceMixin, FolderishMixin, AbstractTrace): """I provide the implementation of ktbs:ComputedTrace . """ _obsels_cls = ComputedTraceObsels ######## ILocalCore (and mixins) implementation ########
######## Protected methods ######## def _empty(self): """I remove all obsels from this trace. Compared to ``remove(None, None, None)``, this method leaves the information about the obsel collection itself. """ with self.edit(_trust=True) as editable: trace_uri = editable.value(None, KTBS.hasObselCollection, self.uri) editable.remove((None, None, None)) self.init_graph(editable, self.uri, trace_uri) FIND_LAST_OBSEL = prepareQuery(""" PREFIX : <http://liris.cnrs.fr/silex/2009/ktbs#> SELECT ?o { ?o :hasEnd ?e . FILTER ( !BOUND(?last_end) || (?e >= ?last_end) ) } ORDER BY DESC(?e) LIMIT 1 """) _REFRESH_VALUES = { "no": 0, "default": 1, "yes": 2, "force": 2, "recursive": 3, None: 1, }
super(BaseMixin, self).remove() parent.force_state_refresh() ######## Private methods ######## def _iter_contained(self): """ Yield the URI and type of every element of this base. """ for s, t, _ in iter(self.state.query(_ITER_CONTAINED_QUERY, initBindings={"base": self.uri})): yield s, t _ITER_CONTAINED_QUERY = prepareQuery(""" PREFIX k: <http://liris.cnrs.fr/silex/2009/ktbs#> SELECT DISTINCT ?s ?t $base # selected solely to please Virtuoso WHERE { $base k:contains ?s . ?s a ?t . } """) # NB: the register_wrapper below does not mean that KTBS.DataGraph is the # *only* type corresponding to InBaseMixin, but this particulat type has no # mixin of its own, so we register it directly here. @register_wrapper(KTBS.DataGraph) @extend_api class InBaseMixin(KtbsResourceMixin): """ Common mixin for all elements of a trace base. """ #pylint: disable-msg=R0903
:class:`rdfrest.cores.mixins.GraphPostableMixin.get_created_class` Only obsels can be posted to a trace. """ # unused arguments #pylint: disable=W0613 # self is not used #pylint: disable=R0201 return Obsel # the following query gets all the candidate obsels in a POSTed graph, # and orders them correctly, guessing implicit values _SELECT_CANDIDATE_OBSELS = prepareQuery(""" PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> PREFIX : <%s#> SELECT ?obs (IF(bound(?b), ?b, "INF"^^xsd:float) as ?begin) (IF(bound(?e), ?e, ?begin) as ?end) $trace # selected solely to please Virtuoso WHERE { ?obs :hasTrace ?trace OPTIONAL { ?obs :hasBegin ?b } OPTIONAL { ?obs :hasEnd ?e } } ORDER BY ?begin ?end """ % KTBS_NS_URI) class ComputedTrace(ComputedTraceMixin, FolderishMixin, AbstractTrace): """I provide the implementation of ktbs:ComputedTrace . """ _obsels_cls = ComputedTraceObsels ######## ILocalCore (and mixins) implementation ########
def get_sparql_query(self, query, namespaces_dict): return self.__query_cache.get(query, False) \ or self.__query_cache.setdefault(query, prepareQuery(query, initNs=namespaces_dict))
subj = [ i[0] for i in tuples ] if len(subj) == 1: subj = subj[0] yield u"""%s%s "%s": %s""" % (comma, indent, pred_conv(pred), val2json(subj, indent)) comma = "," if comma is not None: yield u"%s}" % indent OTHER_ARCS = prepareQuery(""" PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> PREFIX skos: <http://www.w3.org/2004/02/skos/core#> PREFIX : <%s#> SELECT ?subj ?pred ?obj ?trc { ?subj ?pred ?obj . OPTIONAL { ?obj :hasTrace ?trc. ?subj :hasTrace ?trc. } FILTER( !regex(str(?pred), "^%s") && ?pred NOT IN (rdf:type, skos:prefLabel) ) } ORDER BY ?pred ?obj """ % (KTBS_NS_URI, KTBS_NS_URI)) JSONLD = "application/ld+json" JSON = "application/json" @register_serializer(JSONLD, "jsonld", 85, KTBS.KtbsRoot) @register_serializer(JSON, "json", 60, KTBS.KtbsRoot) @wrap_exceptions(SerializeError)
def _get_uri(self, output_uri_type, input_uri_type, input_uri): if output_uri_type == "collection": if input_uri_type == "item": query = prepareQuery("""select ?collection where{ ?collection a dcmitype:Collection. ?item dc:isPartOf ?collection. }""", initNs = {"dc":DC, "dcmitype":DCMITYPE}) elif input_uri_type == "document": query = prepareQuery("""select ?collection where{ ?collection a dcmitype:Collection. ?item dc:isPartOf ?collection. ?item ausnc:document ?document. }""", initNs = {"dc":DC, "dcmitype":DCMITYPE, "ausnc":AUSNC}) elif input_uri_type == "source": query = prepareQuery("""select ?collection where{ ?collection a dcmitype:Collection. ?item dc:isPartOf ?collection. ?item ausnc:document ?document. ?document dc:source ?source. }""", initNs = {"dc":DC, "dcmitype":DCMITYPE, "ausnc":AUSNC}) elif input_uri_type == "annotation": query = prepareQuery("""select ?collection where{ ?collection a dcmitype:Collection. ?item dc:isPartOf ?collection. ?annCollID dada:annotates ?item. ?annotation dada:partof ?annCollID. }""", initNs = {"dc":DC, "dcmitype":DCMITYPE, "dada":DADA}) else: return [] elif output_uri_type == "item": if input_uri_type == "collection": query = prepareQuery("""select ?item where{ ?collection a dcmitype:Collection. ?item dc:isPartOf ?collection. }""", initNs = {"dc":DC, "dcmitype":DCMITYPE}) elif input_uri_type == "itemlist": query = prepareQuery("""select ?item where{ ?itemlistid a localterms:itemList. ?item dc:isPartOf ?itemlistid. }""", initNs = {"localterms":LOCALTERMS, "dc":DC}) elif input_uri_type == "annotation": query = prepareQuery("""select ?item where{ ?annCollID dada:annotates ?item. ?annotation dada:partof ?annCollID. }""", initNs = {"dada":DADA}) elif input_uri_type == "document": query = prepareQuery("""select ?item where{ ?item ausnc:document ?document. }""", initNs = {"ausnc":AUSNC}) elif input_uri_type == "source": query = prepareQuery("""select ?item where{ ?item ausnc:document ?document. ?document dc:source ?source. }""", initNs = {"dc":DC, "ausnc":AUSNC}) else: return [] elif output_uri_type == "annotation": if input_uri_type == "item": query = prepareQuery("""select ?annotation where{ ?annotation dada:partof ?annCollID. ?annCollID dada:annotates ?item. }""", initNs = {"dada":DADA}) else: return [] initBindings={input_uri_type: URIRef(input_uri)} results = self.graph.query(query, initBindings=initBindings) output = [result[output_uri_type].toPython() for result in results.bindings] return output
In order to clearly differenciate attributes from relations, related obsels must be linked to the trace by the ktbs:hasTrace. :param node: the node (uri or blank) to return a description of :param graph: the graph from which to retrieve the description :param fill: if provided, fill this graph rather than a fresh one, and return it """ ret = bounded_description(node, graph, fill) trace_uri = ret.value(node, KTBS.hasTrace) add = ret.add for other, _, in graph.query(_RELATED_OBSELS, initBindings = { "obs": node }): add((other, KTBS.hasTrace, trace_uri)) return ret _RELATED_OBSELS = prepareQuery(""" SELECT DISTINCT ?other $obs # selected solely to please Virtuoso { { $obs ?pred ?other . } UNION { ?other ?pred $obs . } $obs <%s> ?trace . ?other <%s> ?trace . } """ % (KTBS.hasTrace, KTBS.hasTrace)) _NON_ALPHA = re.compile(r'[^\w]+') _NOW = datetime.now