[{"data":1,"prerenderedAt":1448},["ShallowReactive",2],{"doc:\u002Fpyqgis-cartography-visualization\u002Ftemporal-and-3d-visualization\u002Fset-layer-temporal-properties-pyqgis":3},{"id":4,"title":5,"body":6,"description":1437,"extension":1438,"meta":1439,"navigation":270,"path":1444,"seo":1445,"stem":1446,"__hash__":1447},"docs\u002Fpyqgis-cartography-visualization\u002Ftemporal-and-3d-visualization\u002Fset-layer-temporal-properties-pyqgis\u002Findex.md","Set Layer Temporal Properties in PyQGIS",{"type":7,"value":8,"toc":1423},"minimark",[9,13,17,26,203,208,230,234,237,342,365,368,403,408,412,415,452,461,464,497,502,506,585,588,734,750,754,757,896,909,913,916,1180,1185,1188,1192,1195,1253,1258,1262,1283,1287,1332,1336,1345,1349,1355,1361,1379,1385,1389,1419],[10,11,5],"h1",{"id":12},"set-layer-temporal-properties-in-pyqgis",[14,15,16],"p",{},"Temporal properties are the layer's half of QGIS's time support: they say which field holds a feature's time, how long the feature should be considered present, and whether any of that is switched on. Configure them wrongly and the layer either shows everything all the time or nothing at any time, with no error either way.",[14,18,19,20,25],{},"This recipe belongs to ",[21,22,24],"a",{"href":23},"\u002Fpyqgis-cartography-visualization\u002Ftemporal-and-3d-visualization\u002F","Temporal & 3D Visualization in PyQGIS",". It covers the five vector modes, the raster modes, field types that QGIS will and will not accept as dates, feature duration, and the checks that tell you whether the configuration is doing anything.",[14,27,28],{},[29,30,35,39,43,50,59,69,76,80,84,90,96,100,109,113,116,131,135,138,153,157,160,174,178,181,185,190,198],"svg",{"viewBox":31,"role":32,"ariaLabel":33,"xmlns":34},"0 0 760 338","img","The five vector temporal modes shown as what each requires and what each produces on a timeline","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg",[36,37,38],"title",{},"Five modes, chosen by the shape of your data",[40,41,42],"desc",{},"A fixed range applies one interval to the whole layer. An instant per feature marks a single moment. A start and end pair gives each feature its own interval. A start plus a duration field computes the interval. Redraw only re-renders each frame without filtering anything.",[44,45],"rect",{"x":46,"y":46,"width":47,"height":48,"fill":49},"0","760","338","#f6f3ea",[51,52,58],"text",{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"380","26","text-anchor:middle;font-size:14px;font-weight:bold;font-family:sans-serif","#17211d","middle","Match the mode to what the table actually holds",[44,60],{"x":61,"y":62,"width":63,"height":64,"rx":65,"fill":66,"stroke":67,"style":68},"24","44","712","256","10","#fffdf7","#59645f","stroke-width:2",[51,70,75],{"x":71,"y":72,"style":73,"fill":74},"46","72","font-size:11px;font-weight:bold;font-family:sans-serif","#0f766e","mode",[51,77,79],{"x":78,"y":72,"style":73,"fill":74},"330","needs",[51,81,83],{"x":82,"y":72,"style":73,"fill":74},"540","on the timeline",[85,86],"line",{"x1":62,"y1":87,"x2":88,"y2":87,"stroke":67,"style":89},"82","716","stroke-width:1.4",[51,91,95],{"x":71,"y":92,"style":93,"fill":94},"108","font-size:10.5px;font-family:sans-serif","#2f3b35","FixedTemporalRange",[51,97,99],{"x":78,"y":92,"style":98,"fill":67},"font-size:10px;font-family:sans-serif","one range for the layer",[44,101],{"x":82,"y":102,"width":103,"height":104,"rx":105,"fill":106,"stroke":107,"style":108},"98","160","12","3","#e8efe6","#15803d","stroke-width:1.6",[51,110,112],{"x":71,"y":111,"style":93,"fill":94},"146","FeatureDateTimeInstantFromField",[51,114,115],{"x":78,"y":111,"style":98,"fill":67},"one date field",[117,118,120,125,128],"g",{"fill":119},"#2563eb",[44,121],{"x":122,"y":123,"width":124,"height":104},"548","136","5",[44,126],{"x":127,"y":123,"width":124,"height":104},"596",[44,129],{"x":130,"y":123,"width":124,"height":104},"652",[51,132,134],{"x":71,"y":133,"style":93,"fill":94},"184","FeatureDateTimeStartAndEndFromFields",[51,136,137],{"x":78,"y":133,"style":98,"fill":67},"two date fields",[117,139,141,145,149],{"fill":140,"stroke":119,"style":108},"#dbeafe",[44,142],{"x":143,"y":144,"width":71,"height":104,"rx":105},"544","174",[44,146],{"x":147,"y":144,"width":148,"height":104,"rx":105},"602","34",[44,150],{"x":151,"y":144,"width":152,"height":104,"rx":105},"648","56",[51,154,156],{"x":71,"y":155,"style":93,"fill":94},"222","FeatureDateTimeStartAndDurationFromFields",[51,158,159],{"x":78,"y":155,"style":98,"fill":67},"a date and a number",[117,161,164,168,171],{"fill":162,"stroke":163,"style":108},"#f3e9d8","#b45309",[44,165],{"x":143,"y":166,"width":167,"height":104,"rx":105},"212","30",[44,169],{"x":170,"y":166,"width":167,"height":104,"rx":105},"600",[44,172],{"x":173,"y":166,"width":167,"height":104,"rx":105},"660",[51,175,177],{"x":71,"y":176,"style":93,"fill":94},"260","RedrawLayerOnly",[51,179,180],{"x":78,"y":176,"style":98,"fill":67},"nothing",[44,182],{"x":82,"y":183,"width":103,"height":104,"rx":105,"fill":184,"stroke":67,"style":108},"250","#efeadd",[51,186,189],{"x":71,"y":187,"style":98,"fill":188},"286","#b91c1c","every mode also needs setIsActive(True) — the properties are ignored without it",[44,191],{"x":192,"y":193,"width":194,"height":61,"rx":195,"fill":196,"stroke":163,"style":197},"120","310","520","6","#fdf2e2","stroke-width:1.8",[51,199,202],{"x":53,"y":200,"style":201,"fill":94,"textAnchor":57},"327","text-anchor:middle;font-size:10.5px;font-family:sans-serif","redraw-only exists for data-defined symbols that read the frame time themselves",[204,205,207],"h2",{"id":206},"prerequisites","Prerequisites",[209,210,211,219,222],"ul",{},[212,213,214,218],"li",{},[215,216,217],"strong",{},"QGIS 3.34 LTR"," or newer. The temporal framework arrived in 3.14 and settled by 3.20.",[212,220,221],{},"A layer with a date, datetime or ISO-8601 string field.",[212,223,224,225,229],{},"For animation, the controller half — see ",[21,226,228],{"href":227},"\u002Fpyqgis-cartography-visualization\u002Ftemporal-and-3d-visualization\u002Fanimate-with-temporal-controller-pyqgis\u002F","animating with the temporal controller",".",[204,231,233],{"id":232},"an-instant-per-feature","An instant per feature",[14,235,236],{},"The commonest case: each row is an event with one timestamp.",[238,239,244],"pre",{"className":240,"code":241,"language":242,"meta":243,"style":243},"language-python shiki shiki-themes github-dark","from qgis.core import QgsProject, Qgis\n\nincidents = QgsProject.instance().mapLayersByName(\"incidents\")[0]\nprops = incidents.temporalProperties()\n\nprops.setMode(Qgis.VectorTemporalMode.FeatureDateTimeInstantFromField)\nprops.setStartField(\"reported_at\")\nprops.setIsActive(True)\n","python","",[245,246,247,265,272,297,308,313,319,331],"code",{"__ignoreMap":243},[248,249,251,255,259,262],"span",{"class":85,"line":250},1,[248,252,254],{"class":253},"snl16","from",[248,256,258],{"class":257},"s95oV"," qgis.core ",[248,260,261],{"class":253},"import",[248,263,264],{"class":257}," QgsProject, Qgis\n",[248,266,268],{"class":85,"line":267},2,[248,269,271],{"emptyLinePlaceholder":270},true,"\n",[248,273,275,278,281,284,288,291,294],{"class":85,"line":274},3,[248,276,277],{"class":257},"incidents ",[248,279,280],{"class":253},"=",[248,282,283],{"class":257}," QgsProject.instance().mapLayersByName(",[248,285,287],{"class":286},"sU2Wk","\"incidents\"",[248,289,290],{"class":257},")[",[248,292,46],{"class":293},"sDLfK",[248,295,296],{"class":257},"]\n",[248,298,300,303,305],{"class":85,"line":299},4,[248,301,302],{"class":257},"props ",[248,304,280],{"class":253},[248,306,307],{"class":257}," incidents.temporalProperties()\n",[248,309,311],{"class":85,"line":310},5,[248,312,271],{"emptyLinePlaceholder":270},[248,314,316],{"class":85,"line":315},6,[248,317,318],{"class":257},"props.setMode(Qgis.VectorTemporalMode.FeatureDateTimeInstantFromField)\n",[248,320,322,325,328],{"class":85,"line":321},7,[248,323,324],{"class":257},"props.setStartField(",[248,326,327],{"class":286},"\"reported_at\"",[248,329,330],{"class":257},")\n",[248,332,334,337,340],{"class":85,"line":333},8,[248,335,336],{"class":257},"props.setIsActive(",[248,338,339],{"class":293},"True",[248,341,330],{"class":257},[14,343,344,347,348,351,352,356,357,360,361,364],{},[215,345,346],{},"Breakdown:"," ",[245,349,350],{},"setStartField"," takes the field ",[353,354,355],"em",{},"name",", not an index, which is one of the few places in the API where that is true — passing an integer fails silently by matching no field. On QGIS 3.28 and earlier the enum lives at ",[245,358,359],{},"QgsVectorLayerTemporalProperties.ModeFeatureDateTimeInstantFromField","; the scoped ",[245,362,363],{},"Qgis.VectorTemporalMode"," names arrived in 3.30 and both work on current releases. Nothing validates that the named field holds dates: point it at a text column of free-form dates and every feature simply has no time, which renders as an empty map.",[14,366,367],{},"An instant has no width, so on any realistic frame duration a feature is visible in the single frame that contains it. Give it one:",[238,369,371],{"className":240,"code":370,"language":242,"meta":243,"style":243},"from qgis.core import QgsInterval\n\nprops.setFixedDuration(4.0)\nprops.setDurationUnits(Qgis.TemporalUnit.Hours)\n",[245,372,373,384,388,398],{"__ignoreMap":243},[248,374,375,377,379,381],{"class":85,"line":250},[248,376,254],{"class":253},[248,378,258],{"class":257},[248,380,261],{"class":253},[248,382,383],{"class":257}," QgsInterval\n",[248,385,386],{"class":85,"line":267},[248,387,271],{"emptyLinePlaceholder":270},[248,389,390,393,396],{"class":85,"line":274},[248,391,392],{"class":257},"props.setFixedDuration(",[248,394,395],{"class":293},"4.0",[248,397,330],{"class":257},[248,399,400],{"class":85,"line":299},[248,401,402],{"class":257},"props.setDurationUnits(Qgis.TemporalUnit.Hours)\n",[14,404,405,407],{},[215,406,346],{}," This widens every feature's window by the same amount without touching the data, turning a flickering animation into one where patterns are visible. It is a display decision and it changes what the map claims — a four-hour window on incident data implies each incident persisted for four hours, which it did not. Say so in the legend or the caption. Where the true durations vary and are known, the start-and-duration mode below is the honest alternative.",[204,409,411],{"id":410},"a-start-and-an-end","A start and an end",[14,413,414],{},"For things that persist — closures, tenancies, permits — two fields give each feature its own interval.",[238,416,418],{"className":240,"code":417,"language":242,"meta":243,"style":243},"props.setMode(Qgis.VectorTemporalMode.FeatureDateTimeStartAndEndFromFields)\nprops.setStartField(\"valid_from\")\nprops.setEndField(\"valid_to\")\nprops.setIsActive(True)\n",[245,419,420,425,434,444],{"__ignoreMap":243},[248,421,422],{"class":85,"line":250},[248,423,424],{"class":257},"props.setMode(Qgis.VectorTemporalMode.FeatureDateTimeStartAndEndFromFields)\n",[248,426,427,429,432],{"class":85,"line":267},[248,428,324],{"class":257},[248,430,431],{"class":286},"\"valid_from\"",[248,433,330],{"class":257},[248,435,436,439,442],{"class":85,"line":274},[248,437,438],{"class":257},"props.setEndField(",[248,440,441],{"class":286},"\"valid_to\"",[248,443,330],{"class":257},[248,445,446,448,450],{"class":85,"line":299},[248,447,336],{"class":257},[248,449,339],{"class":293},[248,451,330],{"class":257},[14,453,454,456,457,460],{},[215,455,346],{}," A null end field means \"still open\", and QGIS treats it as extending indefinitely, which is exactly the behaviour a live register needs. A null ",[353,458,459],{},"start"," means the feature never appears — which is the right behaviour but catches people out on datasets where a null start was meant as \"always\". Filling those with a sentinel far in the past is the usual fix, and doing it in the data rather than in code keeps it visible.",[14,462,463],{},"The start-and-duration variant is the same idea where the table holds a length rather than an end:",[238,465,467],{"className":240,"code":466,"language":242,"meta":243,"style":243},"props.setMode(Qgis.VectorTemporalMode.FeatureDateTimeStartAndDurationFromFields)\nprops.setStartField(\"began_at\")\nprops.setDurationField(\"hours_open\")\nprops.setDurationUnits(Qgis.TemporalUnit.Hours)\n",[245,468,469,474,483,493],{"__ignoreMap":243},[248,470,471],{"class":85,"line":250},[248,472,473],{"class":257},"props.setMode(Qgis.VectorTemporalMode.FeatureDateTimeStartAndDurationFromFields)\n",[248,475,476,478,481],{"class":85,"line":267},[248,477,324],{"class":257},[248,479,480],{"class":286},"\"began_at\"",[248,482,330],{"class":257},[248,484,485,488,491],{"class":85,"line":274},[248,486,487],{"class":257},"props.setDurationField(",[248,489,490],{"class":286},"\"hours_open\"",[248,492,330],{"class":257},[248,494,495],{"class":85,"line":299},[248,496,402],{"class":257},[14,498,499,501],{},[215,500,346],{}," The duration units apply to the whole field, so a table mixing minutes and hours in one column cannot be expressed here — compute a single-unit column first. A null or zero duration collapses the feature to an instant, with the flicker problem described above.",[204,503,505],{"id":504},"what-qgis-accepts-as-a-date","What QGIS accepts as a date",[14,507,508],{},[29,509,512,515,518,521,524,530,534,539,543,546,550,554,557,561,565,568,571,574,577,581],{"viewBox":510,"role":32,"ariaLabel":511,"xmlns":34},"0 0 760 300","Field types QGIS reads as dates: native date and datetime types work directly, ISO-8601 strings are parsed, and epoch integers and locale-formatted strings are not recognised",[36,513,514],{},"Not every column that looks like a date is one",[40,516,517],{},"Native date and datetime field types are read directly. Strings in ISO 8601 form are parsed successfully. Integers holding epoch seconds and strings in a local format such as day slash month slash year are not recognised, and produce a layer in which no feature has a time.",[44,519],{"x":46,"y":46,"width":47,"height":520,"fill":49},"300",[51,522,523],{"x":53,"y":54,"style":55,"fill":56,"textAnchor":57},"The failure is silent — no feature simply has a time",[44,525],{"x":61,"y":71,"width":526,"height":527,"rx":65,"fill":528,"stroke":107,"style":529},"344","196","#edf8e9","stroke-width:2.5",[51,531,533],{"x":527,"y":72,"style":532,"fill":107,"textAnchor":57},"text-anchor:middle;font-size:11.5px;font-weight:bold;font-family:sans-serif","read as a date",[51,535,538],{"x":71,"y":536,"style":537,"fill":94},"104","font-size:10.5px;font-family:monospace","QDate \u002F QDateTime field",[51,540,542],{"x":71,"y":541,"style":537,"fill":94},"132","\"2026-06-04\"",[51,544,545],{"x":71,"y":103,"style":537,"fill":94},"\"2026-06-04T10:30:00\"",[51,547,549],{"x":71,"y":548,"style":537,"fill":94},"188","\"2026-06-04 10:30:00\"",[51,551,553],{"x":71,"y":552,"style":98,"fill":67},"220","a GeoPackage keeps the native type",[44,555],{"x":556,"y":71,"width":526,"height":527,"rx":65,"fill":66,"stroke":188,"style":529},"392",[51,558,560],{"x":559,"y":72,"style":532,"fill":188,"textAnchor":57},"564","not read as a date",[51,562,564],{"x":563,"y":536,"style":537,"fill":94},"414","1780567800  (epoch seconds)",[51,566,567],{"x":563,"y":541,"style":537,"fill":94},"\"04\u002F06\u002F2026\"",[51,569,570],{"x":563,"y":103,"style":537,"fill":94},"\"4 June 2026\"",[51,572,573],{"x":563,"y":548,"style":537,"fill":94},"\"20260604\"",[51,575,576],{"x":563,"y":552,"style":98,"fill":67},"a shapefile flattens datetime to date",[44,578],{"x":579,"y":580,"width":170,"height":167,"rx":195,"fill":196,"stroke":163,"style":197},"80","262",[51,582,584],{"x":53,"y":583,"style":201,"fill":94,"textAnchor":57},"282","convert once into a real datetime field rather than fighting the parser",[14,586,587],{},"Where the column is not in an accepted form, compute a proper one rather than hoping:",[238,589,591],{"className":240,"code":590,"language":242,"meta":243,"style":243},"from qgis.core import QgsField\nfrom qgis.PyQt.QtCore import QVariant, QDateTime\n\nincidents.startEditing()\nif incidents.fields().indexOf(\"reported_dt\") == -1:\n    incidents.addAttribute(QgsField(\"reported_dt\", QVariant.DateTime))\n    incidents.updateFields()\n\nindex = incidents.fields().indexOf(\"reported_dt\")\nfor feature in incidents.getFeatures():\n    stamp = QDateTime.fromSecsSinceEpoch(int(feature[\"epoch_s\"]))\n    incidents.changeAttributeValue(feature.id(), index, stamp)\nincidents.commitChanges()\n",[245,592,593,604,616,620,625,651,661,666,670,684,699,722,728],{"__ignoreMap":243},[248,594,595,597,599,601],{"class":85,"line":250},[248,596,254],{"class":253},[248,598,258],{"class":257},[248,600,261],{"class":253},[248,602,603],{"class":257}," QgsField\n",[248,605,606,608,611,613],{"class":85,"line":267},[248,607,254],{"class":253},[248,609,610],{"class":257}," qgis.PyQt.QtCore ",[248,612,261],{"class":253},[248,614,615],{"class":257}," QVariant, QDateTime\n",[248,617,618],{"class":85,"line":274},[248,619,271],{"emptyLinePlaceholder":270},[248,621,622],{"class":85,"line":299},[248,623,624],{"class":257},"incidents.startEditing()\n",[248,626,627,630,633,636,639,642,645,648],{"class":85,"line":310},[248,628,629],{"class":253},"if",[248,631,632],{"class":257}," incidents.fields().indexOf(",[248,634,635],{"class":286},"\"reported_dt\"",[248,637,638],{"class":257},") ",[248,640,641],{"class":253},"==",[248,643,644],{"class":253}," -",[248,646,647],{"class":293},"1",[248,649,650],{"class":257},":\n",[248,652,653,656,658],{"class":85,"line":315},[248,654,655],{"class":257},"    incidents.addAttribute(QgsField(",[248,657,635],{"class":286},[248,659,660],{"class":257},", QVariant.DateTime))\n",[248,662,663],{"class":85,"line":321},[248,664,665],{"class":257},"    incidents.updateFields()\n",[248,667,668],{"class":85,"line":333},[248,669,271],{"emptyLinePlaceholder":270},[248,671,673,676,678,680,682],{"class":85,"line":672},9,[248,674,675],{"class":257},"index ",[248,677,280],{"class":253},[248,679,632],{"class":257},[248,681,635],{"class":286},[248,683,330],{"class":257},[248,685,687,690,693,696],{"class":85,"line":686},10,[248,688,689],{"class":253},"for",[248,691,692],{"class":257}," feature ",[248,694,695],{"class":253},"in",[248,697,698],{"class":257}," incidents.getFeatures():\n",[248,700,702,705,707,710,713,716,719],{"class":85,"line":701},11,[248,703,704],{"class":257},"    stamp ",[248,706,280],{"class":253},[248,708,709],{"class":257}," QDateTime.fromSecsSinceEpoch(",[248,711,712],{"class":293},"int",[248,714,715],{"class":257},"(feature[",[248,717,718],{"class":286},"\"epoch_s\"",[248,720,721],{"class":257},"]))\n",[248,723,725],{"class":85,"line":724},12,[248,726,727],{"class":257},"    incidents.changeAttributeValue(feature.id(), index, stamp)\n",[248,729,731],{"class":85,"line":730},13,[248,732,733],{"class":257},"incidents.commitChanges()\n",[14,735,736,347,738,741,742,745,746,749],{},[215,737,346],{},[245,739,740],{},"QVariant.DateTime"," is the field type that survives into GeoPackage as a real datetime; a shapefile will silently truncate it to a date, losing the time of day, which is one more reason not to keep temporal data in shapefiles. ",[245,743,744],{},"fromSecsSinceEpoch"," treats the number as UTC, so if the source is local time you need ",[245,747,748],{},"setTimeSpec"," afterwards or every feature is offset by the timezone difference — an error that shifts a whole animation and looks entirely plausible. Doing this once as a data preparation step, rather than in the rendering code, is what makes the layer reusable.",[204,751,753],{"id":752},"raster-layers","Raster layers",[14,755,756],{},"Raster temporal properties are simpler, because a raster is one thing at one time.",[238,758,760],{"className":240,"code":759,"language":242,"meta":243,"style":243},"from qgis.core import QgsDateTimeRange\nfrom qgis.PyQt.QtCore import QDateTime, QDate, QTime\n\nscene = QgsProject.instance().mapLayersByName(\"s2_20260604\")[0]\nraster_props = scene.temporalProperties()\nraster_props.setMode(Qgis.RasterTemporalMode.FixedTemporalRange)\nraster_props.setFixedTemporalRange(QgsDateTimeRange(\n    QDateTime(QDate(2026, 6, 4), QTime(10, 25)),\n    QDateTime(QDate(2026, 6, 4), QTime(10, 35)),\n))\nraster_props.setIsActive(True)\n",[245,761,762,773,784,788,806,816,821,826,857,882,887],{"__ignoreMap":243},[248,763,764,766,768,770],{"class":85,"line":250},[248,765,254],{"class":253},[248,767,258],{"class":257},[248,769,261],{"class":253},[248,771,772],{"class":257}," QgsDateTimeRange\n",[248,774,775,777,779,781],{"class":85,"line":267},[248,776,254],{"class":253},[248,778,610],{"class":257},[248,780,261],{"class":253},[248,782,783],{"class":257}," QDateTime, QDate, QTime\n",[248,785,786],{"class":85,"line":274},[248,787,271],{"emptyLinePlaceholder":270},[248,789,790,793,795,797,800,802,804],{"class":85,"line":299},[248,791,792],{"class":257},"scene ",[248,794,280],{"class":253},[248,796,283],{"class":257},[248,798,799],{"class":286},"\"s2_20260604\"",[248,801,290],{"class":257},[248,803,46],{"class":293},[248,805,296],{"class":257},[248,807,808,811,813],{"class":85,"line":310},[248,809,810],{"class":257},"raster_props ",[248,812,280],{"class":253},[248,814,815],{"class":257}," scene.temporalProperties()\n",[248,817,818],{"class":85,"line":315},[248,819,820],{"class":257},"raster_props.setMode(Qgis.RasterTemporalMode.FixedTemporalRange)\n",[248,822,823],{"class":85,"line":321},[248,824,825],{"class":257},"raster_props.setFixedTemporalRange(QgsDateTimeRange(\n",[248,827,828,831,834,837,839,841,844,847,849,851,854],{"class":85,"line":333},[248,829,830],{"class":257},"    QDateTime(QDate(",[248,832,833],{"class":293},"2026",[248,835,836],{"class":257},", ",[248,838,195],{"class":293},[248,840,836],{"class":257},[248,842,843],{"class":293},"4",[248,845,846],{"class":257},"), QTime(",[248,848,65],{"class":293},[248,850,836],{"class":257},[248,852,853],{"class":293},"25",[248,855,856],{"class":257},")),\n",[248,858,859,861,863,865,867,869,871,873,875,877,880],{"class":85,"line":672},[248,860,830],{"class":257},[248,862,833],{"class":293},[248,864,836],{"class":257},[248,866,195],{"class":293},[248,868,836],{"class":257},[248,870,843],{"class":293},[248,872,846],{"class":257},[248,874,65],{"class":293},[248,876,836],{"class":257},[248,878,879],{"class":293},"35",[248,881,856],{"class":257},[248,883,884],{"class":85,"line":686},[248,885,886],{"class":257},"))\n",[248,888,889,892,894],{"class":85,"line":701},[248,890,891],{"class":257},"raster_props.setIsActive(",[248,893,339],{"class":293},[248,895,330],{"class":257},[14,897,898,900,901,904,905,908],{},[215,899,346],{}," A ten-minute window around the acquisition is a sensible convention: wide enough that a frame lands inside it, narrow enough that two scenes from different days never overlap. ",[245,902,903],{},"QgsDateTimeRange"," includes both ends by default, so back-to-back ranges show both layers for one instant at the boundary; passing ",[245,906,907],{},"includeEnd=False"," avoids that if it matters. Looping this over a folder of dated scenes, deriving the range from the filename, is a dozen lines and turns an image stack into an animation.",[204,910,912],{"id":911},"applying-it-across-a-folder-of-dated-files","Applying it across a folder of dated files",[14,914,915],{},"The raster case scales into a loop, and the interesting part is deriving the range from the filename.",[238,917,919],{"className":240,"code":918,"language":242,"meta":243,"style":243},"import glob\nimport os\nimport re\nfrom qgis.core import QgsRasterLayer\n\npattern = re.compile(r\"(\\d{4})(\\d{2})(\\d{2})\")\n\nfor path in sorted(glob.glob(\"\u002Fdata\u002Fscenes\u002Fs2_*.tif\")):\n    match = pattern.search(os.path.basename(path))\n    if not match:\n        print(\"skipping, no date in name:\", os.path.basename(path))\n        continue\n\n    year, month, day = (int(g) for g in match.groups())\n    layer = QgsRasterLayer(path, os.path.basename(path))\n    temporal = layer.temporalProperties()\n    temporal.setMode(Qgis.RasterTemporalMode.FixedTemporalRange)\n    temporal.setFixedTemporalRange(QgsDateTimeRange(\n        QDateTime(QDate(year, month, day), QTime(0, 0)),\n        QDateTime(QDate(year, month, day), QTime(23, 59, 59)),\n    ))\n    temporal.setIsActive(True)\n    QgsProject.instance().addMapLayer(layer)\n",[245,920,921,928,935,942,953,957,996,1000,1021,1031,1042,1056,1061,1065,1091,1102,1113,1119,1125,1139,1158,1164,1174],{"__ignoreMap":243},[248,922,923,925],{"class":85,"line":250},[248,924,261],{"class":253},[248,926,927],{"class":257}," glob\n",[248,929,930,932],{"class":85,"line":267},[248,931,261],{"class":253},[248,933,934],{"class":257}," os\n",[248,936,937,939],{"class":85,"line":274},[248,938,261],{"class":253},[248,940,941],{"class":257}," re\n",[248,943,944,946,948,950],{"class":85,"line":299},[248,945,254],{"class":253},[248,947,258],{"class":257},[248,949,261],{"class":253},[248,951,952],{"class":257}," QgsRasterLayer\n",[248,954,955],{"class":85,"line":310},[248,956,271],{"emptyLinePlaceholder":270},[248,958,959,962,964,967,970,973,976,979,982,985,987,989,992,994],{"class":85,"line":315},[248,960,961],{"class":257},"pattern ",[248,963,280],{"class":253},[248,965,966],{"class":257}," re.compile(",[248,968,969],{"class":253},"r",[248,971,972],{"class":286},"\"",[248,974,975],{"class":293},"(\\d",[248,977,978],{"class":253},"{4}",[248,980,981],{"class":293},")(\\d",[248,983,984],{"class":253},"{2}",[248,986,981],{"class":293},[248,988,984],{"class":253},[248,990,991],{"class":293},")",[248,993,972],{"class":286},[248,995,330],{"class":257},[248,997,998],{"class":85,"line":321},[248,999,271],{"emptyLinePlaceholder":270},[248,1001,1002,1004,1007,1009,1012,1015,1018],{"class":85,"line":333},[248,1003,689],{"class":253},[248,1005,1006],{"class":257}," path ",[248,1008,695],{"class":253},[248,1010,1011],{"class":293}," sorted",[248,1013,1014],{"class":257},"(glob.glob(",[248,1016,1017],{"class":286},"\"\u002Fdata\u002Fscenes\u002Fs2_*.tif\"",[248,1019,1020],{"class":257},")):\n",[248,1022,1023,1026,1028],{"class":85,"line":672},[248,1024,1025],{"class":257},"    match ",[248,1027,280],{"class":253},[248,1029,1030],{"class":257}," pattern.search(os.path.basename(path))\n",[248,1032,1033,1036,1039],{"class":85,"line":686},[248,1034,1035],{"class":253},"    if",[248,1037,1038],{"class":253}," not",[248,1040,1041],{"class":257}," match:\n",[248,1043,1044,1047,1050,1053],{"class":85,"line":701},[248,1045,1046],{"class":293},"        print",[248,1048,1049],{"class":257},"(",[248,1051,1052],{"class":286},"\"skipping, no date in name:\"",[248,1054,1055],{"class":257},", os.path.basename(path))\n",[248,1057,1058],{"class":85,"line":724},[248,1059,1060],{"class":253},"        continue\n",[248,1062,1063],{"class":85,"line":730},[248,1064,271],{"emptyLinePlaceholder":270},[248,1066,1068,1071,1073,1076,1078,1081,1083,1086,1088],{"class":85,"line":1067},14,[248,1069,1070],{"class":257},"    year, month, day ",[248,1072,280],{"class":253},[248,1074,1075],{"class":257}," (",[248,1077,712],{"class":293},[248,1079,1080],{"class":257},"(g) ",[248,1082,689],{"class":253},[248,1084,1085],{"class":257}," g ",[248,1087,695],{"class":253},[248,1089,1090],{"class":257}," match.groups())\n",[248,1092,1094,1097,1099],{"class":85,"line":1093},15,[248,1095,1096],{"class":257},"    layer ",[248,1098,280],{"class":253},[248,1100,1101],{"class":257}," QgsRasterLayer(path, os.path.basename(path))\n",[248,1103,1105,1108,1110],{"class":85,"line":1104},16,[248,1106,1107],{"class":257},"    temporal ",[248,1109,280],{"class":253},[248,1111,1112],{"class":257}," layer.temporalProperties()\n",[248,1114,1116],{"class":85,"line":1115},17,[248,1117,1118],{"class":257},"    temporal.setMode(Qgis.RasterTemporalMode.FixedTemporalRange)\n",[248,1120,1122],{"class":85,"line":1121},18,[248,1123,1124],{"class":257},"    temporal.setFixedTemporalRange(QgsDateTimeRange(\n",[248,1126,1128,1131,1133,1135,1137],{"class":85,"line":1127},19,[248,1129,1130],{"class":257},"        QDateTime(QDate(year, month, day), QTime(",[248,1132,46],{"class":293},[248,1134,836],{"class":257},[248,1136,46],{"class":293},[248,1138,856],{"class":257},[248,1140,1142,1144,1147,1149,1152,1154,1156],{"class":85,"line":1141},20,[248,1143,1130],{"class":257},[248,1145,1146],{"class":293},"23",[248,1148,836],{"class":257},[248,1150,1151],{"class":293},"59",[248,1153,836],{"class":257},[248,1155,1151],{"class":293},[248,1157,856],{"class":257},[248,1159,1161],{"class":85,"line":1160},21,[248,1162,1163],{"class":257},"    ))\n",[248,1165,1167,1170,1172],{"class":85,"line":1166},22,[248,1168,1169],{"class":257},"    temporal.setIsActive(",[248,1171,339],{"class":293},[248,1173,330],{"class":257},[248,1175,1177],{"class":85,"line":1176},23,[248,1178,1179],{"class":257},"    QgsProject.instance().addMapLayer(layer)\n",[14,1181,1182,1184],{},[215,1183,346],{}," Reporting the files that did not match, rather than skipping them quietly, is what stops a stray naming convention silently dropping a fortnight out of the animation. A whole-day range per scene is the right choice when the acquisition time is unknown — it guarantees a frame at any time of day finds exactly one scene, provided there is at most one scene per day. Where there are several, narrow the ranges around the real acquisition times or they will overlap and the topmost layer will simply win.",[14,1186,1187],{},"Adding the layers in sorted order matters for a different reason: the layer tree order decides which is drawn on top where ranges do overlap, and a sorted-by-date order at least makes that predictable.",[204,1189,1191],{"id":1190},"checking-that-it-works","Checking that it works",[14,1193,1194],{},"The single most useful diagnostic is to ask the layer what range it thinks it covers.",[238,1196,1198],{"className":240,"code":1197,"language":242,"meta":243,"style":243},"extent = incidents.temporalProperties().calculateTemporalExtent(incidents)\nprint(\"layer covers\", extent.begin(), \"to\", extent.end())\nprint(\"active:\", incidents.temporalProperties().isActive())\nprint(\"mode:\", incidents.temporalProperties().mode())\n",[245,1199,1200,1210,1229,1241],{"__ignoreMap":243},[248,1201,1202,1205,1207],{"class":85,"line":250},[248,1203,1204],{"class":257},"extent ",[248,1206,280],{"class":253},[248,1208,1209],{"class":257}," incidents.temporalProperties().calculateTemporalExtent(incidents)\n",[248,1211,1212,1215,1217,1220,1223,1226],{"class":85,"line":267},[248,1213,1214],{"class":293},"print",[248,1216,1049],{"class":257},[248,1218,1219],{"class":286},"\"layer covers\"",[248,1221,1222],{"class":257},", extent.begin(), ",[248,1224,1225],{"class":286},"\"to\"",[248,1227,1228],{"class":257},", extent.end())\n",[248,1230,1231,1233,1235,1238],{"class":85,"line":274},[248,1232,1214],{"class":293},[248,1234,1049],{"class":257},[248,1236,1237],{"class":286},"\"active:\"",[248,1239,1240],{"class":257},", incidents.temporalProperties().isActive())\n",[248,1242,1243,1245,1247,1250],{"class":85,"line":299},[248,1244,1214],{"class":293},[248,1246,1049],{"class":257},[248,1248,1249],{"class":286},"\"mode:\"",[248,1251,1252],{"class":257},", incidents.temporalProperties().mode())\n",[14,1254,1255,1257],{},[215,1256,346],{}," If the printed extent is empty or invalid, the field is not being read as dates and nothing downstream will work — that check alone resolves most \"my animation is blank\" reports. If it prints a sensible range and the map is still blank, the problem is on the controller side rather than here. Printing the mode is worth it because an assignment that silently failed (an old enum name on a new build, say) leaves the mode at its default rather than raising.",[204,1259,1261],{"id":1260},"qgis-version-compatibility","QGIS version compatibility",[14,1263,1264,1265,836,1267,1270,1271,1274,1275,1278,1279,229],{},"Temporal properties arrived in 3.14 and reached their current shape by 3.20. The enum relocation into ",[245,1266,363],{},[245,1268,1269],{},"Qgis.RasterTemporalMode"," and ",[245,1272,1273],{},"Qgis.TemporalUnit"," happened in 3.30; the old class-scoped names remain available. ",[245,1276,1277],{},"calculateTemporalExtent"," has been present throughout. Mesh temporal properties differ in kind and are covered in ",[21,1280,1282],{"href":1281},"\u002Fpyqgis-cartography-visualization\u002Ftemporal-and-3d-visualization\u002Fload-and-style-mesh-layer-pyqgis\u002F","loading and styling a mesh layer",[204,1284,1286],{"id":1285},"troubleshooting","Troubleshooting",[209,1288,1289,1300,1308,1314,1320,1326],{},[212,1290,1291,347,1294,1297,1298,229],{},[215,1292,1293],{},"Nothing is filtered at all.",[245,1295,1296],{},"setIsActive(True)"," was not called, or the mode is ",[245,1299,177],{},[212,1301,1302,1305,1306,229],{},[215,1303,1304],{},"The map is empty at every frame."," The date field is not being parsed — check ",[245,1307,1277],{},[212,1309,1310,1313],{},[215,1311,1312],{},"Features flash for one frame."," Instant mode with no duration. Set a fixed duration or use start-and-end.",[212,1315,1316,1319],{},[215,1317,1318],{},"Everything is shifted by a few hours."," Timezone: epoch values read as UTC when they were local, or the reverse.",[212,1321,1322,1325],{},[215,1323,1324],{},"A feature never appears despite a valid end date."," Its start is null, which means \"never\" rather than \"always\".",[212,1327,1328,1331],{},[215,1329,1330],{},"Times lost after export."," The output format is shapefile, which has no datetime type.",[204,1333,1335],{"id":1334},"conclusion","Conclusion",[14,1337,1338,1339,1341,1342,1344],{},"Choose the mode from what the table holds, name the fields as strings, remember ",[245,1340,1296],{},", and give instants a duration if you want them to be visible. Then confirm with ",[245,1343,1277],{}," before blaming the controller. Almost every blank temporal map is one of those four things.",[204,1346,1348],{"id":1347},"frequently-asked-questions","Frequently Asked Questions",[14,1350,1351,1354],{},[215,1352,1353],{},"Can different layers use different modes in one project?","\nYes. Properties are per layer, and the controller's range is applied to each according to its own mode, which is how an events layer and a raster stack animate together.",[14,1356,1357,1360],{},[215,1358,1359],{},"Do temporal properties affect Processing algorithms?","\nNo. They filter rendering, not the feature source, so an algorithm reading the layer sees every feature regardless of the current frame.",[14,1362,1363,1366,1367,1370,1371,1374,1375,229],{},[215,1364,1365],{},"How do I copy temporal settings between layers?","\nThere is no dedicated copy method, but the settings are part of the layer style, so ",[245,1368,1369],{},"saveNamedStyle","\u002F",[245,1372,1373],{},"loadNamedStyle"," carries them — see ",[21,1376,1378],{"href":1377},"\u002Fpyqgis-cartography-visualization\u002Fprogrammatic-layer-styling\u002Fsave-and-load-qml-style-pyqgis\u002F","saving and loading a QML style",[14,1380,1381,1384],{},[215,1382,1383],{},"Can the duration come from an expression rather than a field?","\nNot directly. Compute the value into a real or virtual field and point the duration field at that.",[204,1386,1388],{"id":1387},"related","Related",[209,1390,1391,1396,1401,1407,1413],{},[212,1392,1393,1395],{},[21,1394,24],{"href":23}," — the guide this recipe belongs to",[212,1397,1398],{},[21,1399,1400],{"href":227},"Animate Layers with the Temporal Controller",[212,1402,1403],{},[21,1404,1406],{"href":1405},"\u002Fpyqgis-cartography-visualization\u002Ftemporal-and-3d-visualization\u002Fexport-temporal-animation-frames-pyqgis\u002F","Export Temporal Animation Frames in PyQGIS",[212,1408,1409],{},[21,1410,1412],{"href":1411},"\u002Fspatial-data-processing-automation\u002Fattribute-tables-and-field-management\u002Fupdate-attribute-values-pyqgis\u002F","Update Attribute Values in PyQGIS",[212,1414,1415],{},[21,1416,1418],{"href":1417},"\u002Fpyqgis-cartography-visualization\u002Fmap-themes-and-layer-visibility\u002Fset-scale-based-visibility-pyqgis\u002F","Set Scale-Based Visibility in PyQGIS",[1420,1421,1422],"style",{},"html pre.shiki code .snl16, html code.shiki .snl16{--shiki-default:#F97583}html pre.shiki code .s95oV, html code.shiki .s95oV{--shiki-default:#E1E4E8}html pre.shiki code .sU2Wk, html code.shiki .sU2Wk{--shiki-default:#9ECBFF}html pre.shiki code .sDLfK, html code.shiki .sDLfK{--shiki-default:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":243,"searchDepth":267,"depth":267,"links":1424},[1425,1426,1427,1428,1429,1430,1431,1432,1433,1434,1435,1436],{"id":206,"depth":267,"text":207},{"id":232,"depth":267,"text":233},{"id":410,"depth":267,"text":411},{"id":504,"depth":267,"text":505},{"id":752,"depth":267,"text":753},{"id":911,"depth":267,"text":912},{"id":1190,"depth":267,"text":1191},{"id":1260,"depth":267,"text":1261},{"id":1285,"depth":267,"text":1286},{"id":1334,"depth":267,"text":1335},{"id":1347,"depth":267,"text":1348},{"id":1387,"depth":267,"text":1388},"Make a vector or raster layer time-aware from Python — pick the right temporal mode, wire the date fields, set a feature duration, and check the range QGIS actually sees.","md",{"slug":1440,"type":1441,"breadcrumb":1442,"datePublished":1443,"dateModified":1443},"set-layer-temporal-properties-pyqgis","article","Set Temporal Properties","2026-09-05","\u002Fpyqgis-cartography-visualization\u002Ftemporal-and-3d-visualization\u002Fset-layer-temporal-properties-pyqgis",{"title":5,"description":1437},"pyqgis-cartography-visualization\u002Ftemporal-and-3d-visualization\u002Fset-layer-temporal-properties-pyqgis\u002Findex","Z3VFLfytf7-6fMMliPC8Kn1lkPvQiv7xYmltnXMqkag",1788563847736]