👋 Next.js Conf 2024: Come build, party, run, and connect with us! See all events

Sorting content by date in Sanity with a custom script

10 replies
Last updated: May 6, 2022
Hey Sanity Land 👋
I'm having a hard time wrapping my head around ordering content based on dateTimes. Is there a way to remove time from
dateTime
fields in Sanity?
I need to be able to sort by multiple fields while having posts maintain chronological order.

The only way I can think about having this work is by adding another field to our schema that is
date
and run a script to add data to these to the existing 3000+ document dataset.
Thoughts?
May 6, 2022, 6:26 PM
Hi User. I think you’re right. Without a substring or slice function I don’t believe you can currently do this solely in GROQ.
May 6, 2022, 6:45 PM
user A
Is there a substring or slice function that's undocumented? 👀
May 6, 2022, 6:46 PM
No, afraid not.
May 6, 2022, 6:47 PM
Any other creative solutions that you are aware of? I can't imagine that this is an uncommon use case or need.
May 6, 2022, 6:47 PM
All I can think of are either a document action to store a date on your document based on the dateTime field, like you suggested, or doing the sort outside of GROQ after you’ve fetched.
May 6, 2022, 6:50 PM
I’ll add this to the internal ticket as a +1.
May 6, 2022, 6:51 PM
Thanks User. Document action is a good call, unfortunately won't work in our case but a great suggestion.
Thank you for the internal ticket. Much appreciated.
May 6, 2022, 6:56 PM
user A
While we're on the topic, what's the most simple way to backfill the data needed?
May 6, 2022, 8:55 PM
I might start with something like this, though I would urge testing on a non-production dataset first to make sure it works as expected.
You’d put the following somewhere in your studio folder and run it with
sanity exec path/to/script.js --with-user-token
, first changing out
NEW_DATE_FIELD
and `DATETIME_FIELD`:

import sanityClient from 'part:@sanity/base/client'

const client = sanityClient.withConfig({ apiVersion: '2022-05-06' })

const fetchDocuments = () =>
  client.fetch(`*[DATETIME_FIELD != null && NEW_DATE_FIELD == null][0...100] {_id, _rev, DATETIME_FIELD, NEW_DATE_FIELD}`)

const buildPatches = docs =>
  docs.map(doc => ({
      id: doc._id,
      patch: {
        set: {NEW_DATE_FIELD: doc.DATETIME_FIELD.slice(0, 10)},
        // this will cause the migration to fail if any of the documents has been
        // modified since it was fetched.
        ifRevisionID: doc._rev
      }
    }))

const createTransaction = patches =>
  patches.reduce((tx, patch) => tx.patch(patch.id, patch.patch), client.transaction())

const commitTransaction = tx => tx.commit()

const migrateNextBatch = async () => {
  const documents = await fetchDocuments()
  const patches = buildPatches(documents)
  if (patches.length === 0) {
    console.log('No more documents to migrate!')
    return null
  }
  console.log(
    `Migrating batch:\n %s`,
    patches.map(patch => `${patch.id} => ${JSON.stringify(patch.patch)}`).join('\n')
  )
  const transaction = createTransaction(patches)
  await commitTransaction(transaction)
  return migrateNextBatch()
}

migrateNextBatch().catch(err => {
  console.error(err)
  process.exit(1)
})
May 6, 2022, 10:26 PM
This will find all the documents where your
DATETIME_FIELD
exists and
NEW_DATE_FIELD
does not (if you exclude the second one, this would keep running forever). It then sets
NEW_DATE_FIELD
to be the first 10 characters of your
DATETIME_FIELD
, which is the date as
YYYY-MM-DD
. Finally, it bundles those up as packages of 100 so you don’t rate limit yourself.
May 6, 2022, 10:28 PM

Sanity– build remarkable experiences at scale

Sanity is a modern headless CMS that treats content as data to power your digital business. Free to get started, and pay-as-you-go on all plans.

Was this answer helpful?